雙重校驗實現單例模式為什么需要volatile關鍵字


我們先來看下雙重校驗模式的標准代碼:

public class Singleton1 {
    private static volatile Singleton1 singleton;

    private Singleton1(){}

    public static Singleton1  getStance(){
        if(singleton == null){

            synchronized (Singleton1.class){
                if (singleton == null){
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }
}

其次,我們應該知道,synchronized 能保證臨界區的原子性、有序性和可見性。volatile 也能保證所修飾對象的可見性,並且還能禁止重排序。
那么問題就來了:既然 volatile 的功能 synchronized基本都具備,那為啥還需要 volatile 修飾單例對象呢?
我找了很多資料和博客,基本都是解釋 new 操作不是原子操作,在 JVM 層面會導致重排序,但是這並不能解釋為什么 volatile 和 synchronized 關於有序性功能的重疊。

      public static Singleton1  getStance(){
      
        if(singleton == null){ // #1
            synchronized (Singleton1.class){
                if (singleton == null){
                    singleton = new Singleton1(); //#2
                }
            }
        }
        return singleton;
    } 
      // 當兩個線程A和B同時進入方法時,加入A搶奪到鎖,則A繼續執行,當A執行到new操作時,由於new操作不是原子操作,且synchronized也不能禁止重排序,
      // 我們首先將new操作原子化:a-開辟內存空間;b-初始化對象;c-將引用賦值給變量
      // 正常的執行順序應該是a-b-c,不禁止重排序的情況下可能是:a-c-b
      // 當線程A執行a-c,即將執行b的時候,由於cpu時間片結束,則有可能會讓步給線程B,
      // 線程B進行第一次判斷,singleton由於已經有了內存指向,並不為空,此時,對象還沒有執行初始化,但已經判斷為true,並且返回了。
      // 此時,就產生了嚴重的錯誤,因此需要 volatile 來禁止重排序。
      

關於這個問題,我思考良久,最后我找到 synchronized 關於有序性的解釋:只能保證有序性卻不能禁止重排序。
很多博客解釋了很多,我起初非常不能理解,因為都沒提到synchronized只能保證有序性卻不能禁止重排序。我覺得這句話才是解釋這個問題的關鍵所在。


免責聲明!

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



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