解決方案
相信大多數同學在面試當中都遇到過手寫單例模式的題目,那麼如何寫乙個完美的單例是面試者需要深究的問題,因為乙個嚴謹的單例模式說不定就直接決定了面試結果,今天我們就要來講講看似執行緒安全的雙重檢測鎖單例模式中可能會出現的指令重排問題。
乍一看下面單例模式沒啥問題,還加了同步鎖保證執行緒安全,從表面上看確實看不出啥問題,當在同一時間多個執行緒同事執行該單例時就會出現jvm指令重排的問題,從而可能導致某乙個執行緒獲取的single物件未初始化物件。
public
class
single
public
static single getinstance()
}}return single;
}}
其實single = new single()這段**並不具備原子性,從**層面上來說確實沒問題,但是如果了解jvm指令的就知道其實在執行這句**的時候在jvm中是需要執行三個指令來完成的,如下:
memory =
allocate()
;//1:分配物件的記憶體空間
ctorinstance
(memory)
;//2:初始化物件
instance = memory;
看到上面指令重排的解釋之後,那麼我們來回顧一下未加volatile修飾符的單例為何會出現問題。
假設有a、b兩個執行緒去呼叫該單例方法,當a執行緒執行到single = new single()時,如果編譯器和處理器對指令重新排序,指令重排後:
memory =
allocate()
;//1:分配物件的記憶體空間
instance = memory;
ctorinstance
(memory)
;//2:初始化物件
當a執行緒執行到第二步(3:設定instance指向剛分配的記憶體位址,此時物件還沒被初始化)變數single指向記憶體位址之後就不為null了,此時b執行緒進入第乙個if,由於single已經不為null了,那麼就不會執行到同步**塊,而是直接返回未初始化物件的變數single,從而導致後續**報錯。
問題也搞清楚了,接下來我們就來看下如何解決這個問題。
解決問題的關鍵就在於volatile關鍵字,我們來看下volatile關鍵字的特性。
可見性:
有序性:
要避免指令重排序,synchronized、lock作用的**塊自然是有序執行的,volatile關鍵字有效的禁止了指令重排序,實現了程式執行的有序性;
看完volatile關鍵字的特性之後我們應該就明白了,是volatile關鍵字禁止了指令重排序從而解決了指令重排的問題。
對比上面單例,下面單例在私有靜態變數single前面加了修飾符volatile,能夠防止jvm指令重排,從而解決了single物件可能出現成員變數未初始化的問題。
public
class
single
public
static single getinstance()
}}return single;
}}
雙重檢測單例模式
在多執行緒場景下,當乙個執行緒判斷instance為null時 他會新建乙個例項,那麼問題來了,當a執行緒發現物件例項為空時,準備 新建乙個例項,這時cpu輪詢到b執行緒,b執行緒也察覺物件例項為空,它也會新建乙個例項,這樣就破壞了單例模式。首先物件例項必須是全域性共享的,用volatile修飾,然...
單例模式 雙重檢查鎖
單例模式分為餓漢式和懶漢式。餓漢式是事先分配記憶體,提前建立。這樣的方式為到位占用資源,當這種比較多時,會占用很多記憶體。懶漢式是在被呼叫的時候進行。這種在併發時又會導致問題。比較穩妥的辦法是在懶漢式的基礎上加上鎖,然後進行雙重檢查,這種springioc容器式單例也是用這種雙重檢查來避免執行緒衝突...
單例模式 雙重校驗鎖
單例模式 雙重校驗鎖 author szekinwin public class singleton3 私有化構造方法 private static volatile singleton3 singleton null public static singleton3 getinstance ret...