十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
對于一個軟件系統(tǒng)的某些類而言,我們無須創(chuàng)建多個實例。舉個大家都熟知的例子——Windows任務(wù)管理器,我們可以做一個這樣的嘗試,在Windows的“任務(wù)欄”的右鍵彈出菜單上多次點擊“啟動任務(wù)管理器”,看能否打開多個任務(wù)管理器窗口?通常情況下,無論我們啟動任務(wù)管理多少次,Windows系統(tǒng)始終只能彈出一個任務(wù)管理器窗口,也就是說在一個Windows系統(tǒng)中,任務(wù)管理器存在唯一性。
實際開發(fā)中,我們也經(jīng)常遇到類似的情況,為了節(jié)約系統(tǒng)資源,有時需要確保系統(tǒng)中某個類只有唯一一個實例,當(dāng)這個唯一實例創(chuàng)建成功之后,我們無法再創(chuàng)建一個同類型的其他對象,所有的操作都只能基于這個唯一實例。為了確保對象的唯一性,我們可以通過單例模式來實現(xiàn),這就是單例模式的動機所在。
單例模式定義如下: 單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。單例模式是一種對象創(chuàng)建型模式。
單例模式有三個要點:
在單例類的內(nèi)部實現(xiàn)只生成一個實例,同時它提供一個靜態(tài)的getInstance()工廠方法,讓客戶可以訪問它的唯一實例;為了防止在外部對其實例化,將其構(gòu)造函數(shù)設(shè)計為私有;在單例類內(nèi)部定義了一個Singleton類型的靜態(tài)對象,作為外部共享的唯一實例。
單例模式一般分為兩種,分別是餓漢式與懶漢式,下面就分別講解這兩種模式,并且詳解餓漢式的雙重校驗鎖
餓漢式對于餓漢式來說,它在定義靜態(tài)變量的時候?qū)嵗瘑卫?,因此在類加載的時候就已經(jīng)創(chuàng)建了單例對象。而且該單例對象用final修飾,之后每次獲取直接返回即可。
public class HungrySingleton {private static final HungrySingleton singleton=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){return singleton;
}
}
懶漢式(雙重校驗鎖)懶漢式單例在第一次調(diào)用getInstance()方法時實例化,在類加載時并不自行實例化,這種技術(shù)又稱為延遲加載(Lazy Load)技術(shù),即需要的時候再加載實例。
先看一下在單線程情況下的懶漢式單例:
public class LazySingleton {private static LazySingleton singleton=null;
private LazySingleton(){}
public static LazySingleton getInstance(){if(singleton==null){singleton=new LazySingleton();
}
return singleton;
}
}
可以看到,在獲取實例的時候,會先判斷一下當(dāng)前的靜態(tài)變量singleton是否為null,如果為null,則為其初始化。如果不為null,則直接返回。
但是這樣的程序在多線程環(huán)境下會出現(xiàn)問題?。?!
在多線程環(huán)境下這樣的代碼會出現(xiàn)問題,因此我們考慮給getInstance()
方法加上同步鎖,防止多個線程同時訪問getInstance()
方法,如下。
public class LazySingleton {private static LazySingleton singleton=null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){if(singleton==null){singleton=new LazySingleton();
}
return singleton;
}
}
但是這樣一來,每次調(diào)用getInstance()時都需要進行線程鎖定判斷,在多線程高并發(fā)訪問環(huán)境中,將會導(dǎo)致系統(tǒng)性能大大降低。那么如何解決該問題呢?有人提出了雙重校驗鎖:
在加鎖之前先判斷一下該靜態(tài)變量是否為null。如果不為null就不需要再加鎖進行初始化了。這樣在高并發(fā)的環(huán)境下就不會出現(xiàn)頻繁獲取鎖的情況了。
public class LazySingletonSyn {private static LazySingletonSyn singleton=null;
private LazySingletonSyn(){}
public static LazySingletonSyn getInstance(){if(singleton==null){synchronized (LazySingletonSyn.class){if(singleton==null){ // 標記點 1
singleton=new LazySingletonSyn();
}
}
}
return singleton;
}
}
現(xiàn)在程序是完美的了嗎?
依然不是!問題出現(xiàn)在singleton=new LazySingletonSyn();
語句
由于現(xiàn)代的處理器大多采用指令級并行技術(shù)。為了提高指令的執(zhí)行效率,在指令執(zhí)行的階段可能會出現(xiàn)重排序的現(xiàn)象。
我們看上面代碼的標記點1singleton=new LazySingletonSyn();
該語句在執(zhí)行的時候會被分解為三條(偽)指令:
memory=allocate(); //1.分配對象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
在上面的偽代碼中,其中2與3可能會被重排序。
這里涉及到了as-if-serial概念。as-if-serial概念是指不管怎么重排序,單線程程序的執(zhí)行結(jié)果不能被改變。而在該語句中,將2,3的執(zhí)行順序改變之后,在單線程的情況下該程序的運行結(jié)果不會被改變。因此2,3可能重排序
一旦被重排序,就可能出現(xiàn)以下的結(jié)果:
如果線程A,B按照下圖的時間執(zhí)行,那么B線程將會得到一個還沒有被初始化的對象??!
問題就出現(xiàn)在2,3的重排序!那么我們只需要禁止2,3重排序即可。
雙重校驗鎖實現(xiàn)懶漢式
我們只需要對上面的代碼進行很小的改動(將singleton聲明為volatile),就可以實現(xiàn)線程安全的懶漢式單例。
public class LazySingletonSyn {private volatile static LazySingletonSyn singleton=null;
private LazySingletonSyn(){}
public static LazySingletonSyn getInstance(){if(singleton==null){synchronized (LazySingletonSyn.class){if(singleton==null){singleton=new LazySingletonSyn();
}
}
}
return singleton;
}
}
解釋如下:
由于singleton被volatile修飾,那么為了實現(xiàn)volatile的內(nèi)存語義(保證singleton在多線程環(huán)境下對共享內(nèi)存的可見性),編譯器在生成字節(jié)碼的時候,會在指令序列中插入內(nèi)存屏障來禁止特定的指令重排序。
由于singleton=new LazySingletonSyn();
是一個寫操作,在該操作的指令之后,JMM(Java內(nèi)存模型)會插入一個storeload內(nèi)存屏障。
此時的內(nèi)存指令變成:
memory=allocate(); //1.分配對象的內(nèi)存空間
ctorInstance(memory); //2. 初始化對象
instance=memory; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
storeload; //4. storeload內(nèi)存屏障
該指令(storeload)會保證在屏障之前的所有內(nèi)存訪問指令全部完成之后,再執(zhí)行該屏障的之后的語句。因此當(dāng)線程B再嘗試singleton==null
的時候,線程A的singleton=new LazySingletonSyn();
以及全部執(zhí)行完成了。因此就不會再出現(xiàn)上面的由于指令2,3重排序?qū)е碌膯栴}了。
其實就是一句話:
JMM會禁止volatile寫與其之后可能存在的volatile讀/寫重排序。因此不會存在上面的圖出現(xiàn)的情況!
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧