DCL單例模式為什么要兩次判空

public class Test {
   private volatile static Test instance;

   private Test() {

   }

   public static Test getInstance() {
       if (instance == null) {
           synchronized (Test.class) {
               if (instance == null) {
                   instance = new Test();
               }
           }
       }
       return instance;
   }
}

解析
第一個注意點:使用私有的構(gòu)造函數(shù),確保正常情況下該類不能被外部初始化(非正常情況比如通過反射初始化,一般使用反射之后單例模式也就失去效果了)。
第二個注意點:getInstance方法中第一個判空條件,邏輯上是可以去除的,去除之后并不影響單例的正確性,但是去除之后效率低。因為去掉之后,不管instance是否已經(jīng)初始化,都會進行synchronized操作,而synchronized是一個重操作消耗性能。加上之后,如果已經(jīng)初始化直接返回結(jié)果,不會進行synchronized操作。
第三個注意點:加上synchronized是為了防止多個線程同時調(diào)用getInstance方法時,各初始化instance一遍的并發(fā)問題。
第四個注意點:getInstance方法中的第二個判空條件是不可以去除,如果去除了,并且剛好有兩個線程a和b都通過了第一個判空條件。此時假設(shè)a先獲得鎖,進入synchronized的代碼塊,初始化instance,a釋放鎖。接著b獲得鎖,進入synchronized的代碼塊,也直接初始化instance,instance被初始化多遍不符合單例模式的要求~。加上第二個判空條件之后,b獲得鎖進入synchronized的代碼塊,此時instance不為空,不執(zhí)行初始化操作。
第五個注意點:instance的聲明有一個voliate關(guān)鍵字,如果不用該關(guān)鍵字,有可能會出現(xiàn)異常。因為instance = new Test();并不是一個原子操作,會被編譯成三條指令,如下所示。
1.給Test的實例分配內(nèi)存 2.初始化Test的構(gòu)造器 3.將instance對象指向分配的內(nèi)存空間(注意 此時instance就不為空)
然后咧,java會指令重排序,JVM根據(jù)處理器的特性,充分利用多級緩存,多核等進行適當(dāng)?shù)闹噶钪嘏判?,使程序在保證業(yè)務(wù)運行的同時,充分利用CPU的執(zhí)行特點,最大的發(fā)揮機器的性能!簡單來說就是jvm執(zhí)行上面三條指令的時候,不一定是1-2-3這樣執(zhí)行,有可能是1-3-2這樣執(zhí)行。如果jvm是按照1-3-2來執(zhí)行的話,當(dāng)1-3執(zhí)行完2還沒執(zhí)行的時候,如果另外一個線程調(diào)用getInstance(),因為3執(zhí)行了此時instance不為空,直接返回instance。問題是2還沒執(zhí)行,此時instance相當(dāng)于什么都沒有,肯定是有問題的。然后咧,voliate有一個特性就是禁止指令重排序,上面的三條指令是按照1-2-3執(zhí)行的,這樣就沒有問題了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容