首先先看一個使用雙重檢查的單例模式:
public class DoubleCheckedLock{
? ? private static DoubleCheckedLock instance;
????public static DoubleCheckedLock getInstance(){
? ? ? ? if(instance == null){
? ? ? ? ? ? synchronized(DoubleCheckdLock.class)
? ? ? ? ? ? if(instance == null){
? ? ? ? ? ? ? ? instance = new DoubleCheckedLock();
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
????}
}
雙檢鎖機制的出現(xiàn)確實是解決了多線程并行中不會出現(xiàn)重復new對象棚蓄,而且也實現(xiàn)了懶加載,但很遺憾的是? ? ? ? ? ? ? instance = new DoubleCheckedLock()在編譯器下實現(xiàn)是有過程的
過程1.? ? 給新的實體instance分配內(nèi)存
過程2.? ? 調(diào)用DoubleCheckedLock的構(gòu)造函數(shù)給instance初始化
過程3.? ? 將incetance指向分配的內(nèi)存空間
在這三個過程之間,由于JVM的“優(yōu)化”機制,如果出現(xiàn)多線程并發(fā)訪問的情況,就會出現(xiàn)A線程在執(zhí)行過程2的時候毒嫡,已經(jīng)分配好了內(nèi)存莹妒,此時想要進入過程3,不巧的是诈茧,線程B進入了過程2,此時instance并不為null(內(nèi)存已經(jīng)被分配),導致線程B此時不執(zhí)行創(chuàng)建對象的語句捂掰,改為直接返回一個未初始化的DoubleCheckedLock對象!
可以使用volatile關(guān)鍵字來定義該變量的語義敢会,使得每次修改instance后,線程工作內(nèi)存強制刷新到主存中这嚣,禁止了JVM的指令重排序鸥昏,防止了該問題
使用volatile另一個方面則使instance對于其他線程可見,這里被鎖住的是DoubleCheckedLock.class而非instance姐帚,如果不使用volatile吏垮,則有可能引發(fā)當前變量更改后只存在于自身線程的工作內(nèi)存中(即未同步到主存)而引發(fā)的多個線程創(chuàng)建多個實例的后果,導致單例模式失去意義罐旗。