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