剛剛接觸單例模式的我們,在閱讀大神寫的代碼的時候,有時很想不通爲什麼這裏要使用單例這種模式,有啥好處嗎,不這樣寫又會咋滴等問題的困擾。下面我就想比較通俗的、用自己的語言組織講解一下單例模式,要是有地方理解不到位或出現偏差,希望大家能及時指出。
1.什麼是單例模式?
2.爲什麼會有這種需求,在哪些地方用單例模式,原因或者好處是什麼?
3.如何創建單例模式?常見的創建方式優缺點。
4.單列和工具類很像,比如math類是這樣使用的Math.double(2,8);有啥區別呢?
1.單例模式:
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
通常我們可以讓一個全局變量使得一個對象被訪問,但它不能防止你實例化多個對象。一個最好的辦法就是,讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例可以被創建,並且它可以提供一個訪問該實例的方法。
簡單的講就是:整個項目,有且只能創建唯一一個這個類的實例。
2.爲啥會有這樣的需求呢?舉個例子在電腦任務欄右鍵然後點擊啓動任務管理器,會彈出一個任務管理器窗口,然後你再重複這樣的操作,看能再彈出一個任務管理器的窗口嗎?肯定不能!要是誰能,請私信我。哈哈哈。。。這裏對彈出窗口唯一限制用的就是單列模式。有且只能創建唯一一個實例即確保對象的唯一性。
爲了確保對象的唯一性我們可以使用單列,但是爲什麼要確保對象的唯一性?
我認爲好處有兩點:
①省資源。項目中大量使用一個類,比如網絡請求框架,要是每次使用創建對象都new的話,很浪費資源的。
比如我封裝的網絡框架使用時是這樣的:ApiService.getInstance().getData().enqueue(//請求回調操作);
②就像彈出任務管理器一樣要統一,只能有一個。
3.創建單列的方式:
最簡單的就是根據單列的概念去創建。即有且只有一個對象。①私有的構造方法,不允許外部代碼通過new的方式創建對象。②獲取對象的方法中加上是否已經存在的判斷,有則直接返回,沒有則new一個。
如下:
/**
* 單利模式總結
* 簡單的講單列要達到什麼效果?整個項目中 有且只能產生一個類的實例
* 我認爲使用單列模式原因主要有以下兩點
* 1.減少內存的佔用 要是在每一次使用的時候都new一個對象,相當佔用資源
* 2.有些情況適合使用單列 比如桌面上彈出任務管理器窗口 是隻能彈出一個的
* 3.如何使用單列?
*/
public final class Singleton {
private static Singleton singleton;
//1.私有的構造 要是修飾public或者不寫的話 類創建對象的時候直接走默認的構造 private意味着對於外部代碼而言不能通過new來實例化 但是類內部可以的
private Singleton() {
}
//2.通過Singleton.getInstance() 獲取唯一實例 加上判斷 存在則返回不存在則直接本類中new 這是最簡單的創建單例方式
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
測試類:
/**
* 測試單例
*/
public class TestSingleTon {
public static void main(String[] args) {
// Singleton singleton = new Singleton(); 提示錯誤:私有的構造不允許new
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
if (instance1 == instance2) {
System.out.println("說明是單例模式,只能創建一個實例");
}
}
}
說明創建了唯一的一個實例。
但是存在一個問題就是,當有多個線程並行調用getInstance()的時候,就會創建多個單列。也就是說在多線程下不能正常工作。最直接的方法就是加鎖,只能一個一個的來,代碼如下:
public static Singleton getInstance() {.... } 改爲 public synchronizeed static Singleton getInstance() {...}
這樣保證了同一時間只能有一個線程調用getInstance()方法。但是效率極低,很少有情況會使用同步啊。(關於同步和異步的概念,在文章末尾有簡單的解釋)
爲了保證多線程訪問我們一般會雙重加鎖:
雙重檢查鎖:double-checked-locking 改變了3,4,5行代碼
public final class Singleton {
private static Singleton singleton;
//1.私有的構造 要是修飾public或者不寫的話 類創建對象的時候直接走默認的構造 private意味着對於外部代碼而言不能通過new來實例化 但是類內部可以的
private Singleton() {
}
//2.通過Singleton.getInstance() 獲取唯一實例 加上判斷 存在則返回不存在則直接本類中new 這是最簡單的創建單例方式
public synchronized static Singleton getInstance() {
if (singleton == null) { //3.多個線程一起進入同步代碼塊的if
synchronized (Singleton.class) { //4.修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;
if (singleton == null) { //5.保證進入一個 判斷是不是有了 有則返回
singleton = new Singleton();
}
}
}
return singleton;
}
}
這樣就解決了多線程訪問單列的問題。
4.對比工具類:
給大家放一個常用的Math類的源代碼:
以下是Math類:
public final class Math {
/**
* Don't let anyone instantiate this class.
*/
private Math() {}
public static native double sin(double a);
}
簡單分析:
①私有的構造不允許外部代碼以new的方式創建對象
②聲明爲final關鍵字不允許被其他類繼承
③提供native修飾的靜態方法,方法本身沒有實現,意味着這樣的計算是使用jni的方式,預計c的計算效率高。
注:Native Method 就是一個java調用非java代碼的接口。該方法沒有具體的java語言實現,比如又可能是c。
④自己書寫工具類的注意final關鍵字等這樣的細節,儘量規範。
⑤哇塞,突然感覺好心酸啊。工具類竟然是一個不能new對象、不能有子類(final),得多麼的寂寞。
以上就是自己對單例模式的理解,有問題請大家積極指出。
PS:線程同步和異步的概念
什麼時候必須同步?什麼叫同步?如何同步?
只要在幾個線程之間共享變量,就必須使用synchronized同步(或者volatite易變的)確保一個線程可以看見另一個線程做的更改。一個線程所做的變化何時以及如何變成對其他線程可見。
同步:共享的資源在同一時刻只能被一個線程使用,這種方式成爲同步。
爲了防止多個線程併發對同一數據的修改,所以需要同步,否則會造成數據不一致,也就是所謂的線程安全。
同步:提交請求=》等待服務器處理=》處理完返回 這個期間客戶端瀏覽器不能幹任何事
異步:請求通過事件觸發=》服務器處理(此時瀏覽器依然可以做其他事情)=》處理完畢。
簡單的講同步即有順序
異步:大家一起上公家車,沒有秩序,可以同時發生。
很多時候我們使用的是異步多線程來處理同一業務裏的大量數據,好比一萬個訂單要處理,如果你使用一個線程順序執行,一個個處理,非常耗時間;但是多線程就可以開100個線程異步處理,這樣效率提高很多。