雙重校驗鎖 --使用volatile和兩次判空校驗


介紹

雙重校驗鎖是單例模式中,餓漢式的一種實現方式。因為有兩次判空校驗,所以叫雙重校驗鎖,一次是在同步代碼塊外,一次是在同步代碼塊內。

為什么在同步代碼塊內還要再檢驗一次?

第一個if減少性能開銷,第二個if避免生成多個對象實例。

現有三個線程A,B,C,假設線程A和線程B同時調用getSingleton()時,判斷第一層if判斷都為空,這時線程A先拿到鎖,線程B在代碼塊外層等待。線程A進行第二層if判斷,條件成立后new了一個新對象,創建完成,釋放鎖,線程B拿到鎖,進行第二層if判斷,singleton不為空,直接返回singleton釋放鎖,避免生成多個對象實例。線程線C調用getSingleton時第一層判斷不成立,直接拿到singleton對象返回,避免進入鎖,減少性能開銷

為什么要用volatile關鍵字?

singleton = new Singleton();這行代碼並不是一個原子指令,可能會在JVM中進行指令重排;

new 實例背后的指令,我們通過使用 javap -c指令,查看字節碼如下:

   // 創建 Singleton 對象實例,分配內存
       0: new           #5                 
    // 復制棧頂地址,並再將其壓入棧頂
       3: dup
    // 調用構造器方法,初始化 Singleton對象
       4: invokespecial #6         // Method "<init>":()V
    // 存入局部方法變量表
       7: astore_1

從字節碼可以看到創建一個對象實例,可以分為三步:

(1)分配對象內存(給singleton分配內存)。

(2)調用構造器方法,執行初始化(調用 Singleton 的構造函數來初始化成員變量)。

(3)將對象引用賦值給變量(執行完這步 singleton 就為非 null 了)。

在 JVM 的即時編譯器中存在指令重排序的優化。指令重排並不影響單線程內的執行結果,但是在多線程內可能會影響結果。也就是說上面的2和3的順序是不能保證的,但是並不會重排序 1 的順序,因為 2,3 指令需要依托 1 指令執行結果。最終的執行順序可能是 1-2-3 也可能是 1-3-2。

1-3-2的情況

上面多線程執行的流程中,如果線程A獲取到鎖進入創建對象實例,這個時候發生了指令重排序。當線程A 執行到 t3 時刻(singleton已經非null了,但是卻沒有初始化),此時線程 B 搶占了,由於此時singleton已經不為 Null,會直接返回 singleton對象,然后使用singleton對象,然而該對象還未初始化,就會報錯。我們只需將 singleton 變量聲明成 volatile 就可以禁止指令重排,避免這種現象發生。 

參考/好文:

菜鳥教程 – 設計模式--https://www.runoob.com/design-pattern/singleton-pattern.html

掘金 --https://juejin.im/post/5d54c2d251882542f27bdff6


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM