并发编程之单例模式
線程安全的單例模式一般認為有三種實現方式: 懶漢模式,枚舉方式,靜態內部類方式; 下面逐個來看下他們的實現方式和實現原理。
(1) 懶漢模式:
public class Singleton { private static volatile Singleton instance;private Singleton(){}/*** 雙重檢查鎖實現可以有效提高效率* 因為在大多數時候多處訪問getInstance 方法時 是不需要創建實例的* 所以外層的null 判斷可以大大的減少排隊等待時間* 而里層的null 判斷是用來在實例創建之初給第一次訪問者用的*/public static Singleton getInstance(){if(null == instance){ // 語句③synchronized (Singleton.class){ // 語句②if(null == instance){ // 語句①instance = new Singleton(); // 語句④}}}return instance;} }對于線程安全的考慮,主要是在獲取單例模式的實例時調用getInstance方法會存在線程安全問題;只所以線程安全的懶漢模式需要如上實現,需要我們做如下考慮:
(1) 判語句①中判斷 null == instance 時 可能有多個線程同時在判斷,那么這些線程如果讀取到的instance 都是null , 那么在第一個線程對instance 賦值之后,后續幾個線程也會執行 ”instance = new Singleton()” 語句,單例模式將變成多例模式
(2) 對于(1) 的情況,我們可以考慮對Singleton 類加鎖(對于語句②),也即在判斷 instance 是否為null 之前做一個同步處理,這樣可以保證不會有多個線程同時對 instance 做空判斷,但是這樣在高并發下會嚴重影響getInstance 方法的調用性能
(3) 鑒于(2) 中加鎖操作的性能問題, 對于那些不是首次調用 getInstance 方法的語句在獲取鎖之前可以先判斷一下 instance 實例是否已經創建好,即先做一次 null == instance (語句③) 的判斷,只有當發現instance 為null 時才繼續去創建instance
(4)? 以上三個步驟的實現,叫做"雙重檢查鎖" 的做法,這樣做看起來似乎已經線程安全且也算高效了,但是對于語句④,我們需要注意的是這個語句不是原子操作,在語句④執行時,可能會出現語句④執行到一半,另外某個線程執行了語句③
這個時候語句③的判斷結果可能會是 null != instance , 但是返回的 instance 也不是一個真正可用的實例,這樣將導致實例剛創建時偶爾會出現 getInstance得到的實例無法正常使用。
具體原因如下:
instance = new Singleton(); 語句可以拆分為以下三個步驟
操作①: objRef = allocate(Singleton.class)
操作②: invokeConstructor(Singleton.class)
操作③: instance =?objRef?
? ? ?由于編譯器在編譯指令時會對語句進行便于CPU流水化執行重排序,因而常會將 操作③ 的執行順序放到操作②之前,因此執行順序就編程了 ①③②; 這樣如果在操作③執行完之后其它線程就開始執行判斷上述代碼段的
? ? 語句③ 時將會直接把 未調用語句②的instance 直接返回,拿到的將是一個未調用構造方法初始化的空實例。
這個的解決辦法也很簡單,只需要在Singleton 屬性的定義時使用 volatile 關鍵字修飾,就能有效的防止 操作③被重排序,由于操作③是一個原子操作,并且操作前 instance == null ,操作后 instance 指向一個初始化
? ?的Singleton實例,因此可以保證在語句③判斷時,只要判斷 instance !=null , 那么getInstance() 獲取到的實例一定是初始化后的實例。
?
以上懶漢線程安全的單例模式近來有人不提倡使用,具體原因可參考原因;
?
(2) 靜態內部類:
public class Singleton{private Singleton(){}public static Singleton getInstance(){return SingletonHolder.INSTANCE;}/*** 靜態內部類*/private static class SingletonHolder{/** 靜態內部類的靜態屬性 */final static Singleton INSTANCE = new Singleton();} }使用靜態內部類方式實現單例模式,主要基于JVM會在一個類的靜態方法或者屬性被調用時初始化該類的靜態屬性。因此當多個線程并發調用,第一個線程執行到getInstance() 方法的SingletonHolder.INSTANCE 時,
靜態內部類 SingletonHolder 會被jvm 初始化,初始化完成之后調用才能繼續; 這樣就能確保包括第一次在內的所有調用都能拿到初始化只有的唯一實例。
?
(3) 枚舉方式:
如果把單例模式定義成一個枚舉,那么線程安全的實現方式可以更加簡單,如下:
public enum Singleton {INSTANCE;Singleton(){}public void someService(){} }在調用時只需要直接使用 Singleton.INSTANCE.someService(); 就可以了, 這里其實和靜態內部類實現原理是一樣的,INSTANCE 實例本身就是 final static 的Singleton 屬性,當INSTANCE被調用,同樣的觸發了初始化,因此可以在所有訪問之前生產單例模式的實例。
?
轉載于:https://www.cnblogs.com/mingorun/p/11222243.html
總結
- 上一篇: 阶段1 语言基础+高级_1-3-Java
- 下一篇: IOS文本框readonly时焦点事件