單例模式雙重校驗寫法及延伸

最近看一些Java內(nèi)存模型方面的書,講了一下Java的對象的內(nèi)存分配過程,其中有個例子講解多線程鎖的問題,說了下面的例子:

單例寫法 雙重校驗寫法

//------------------------雙重校驗鎖------------------
    private static Singleton singleton2;//-------1

    public static Singleton getInstance4() {//------------2
        if (singleton2 == null) {//-----------------------3
            synchronized (Singleton.class) {//------------4
                if (singleton2 == null)//-----------------5
                    singleton2 = new Singleton();//-------6
            }
        }
        return singleton;
    }

問題處在了第6步,Java創(chuàng)建對象的第6步可以分為以下三步:

memory = allocate();//----1
ctorInstance(memory);//-2
instance = memory;//-----3

其中2,3步在JVM編譯優(yōu)化時可能發(fā)生重排序,這和采用的JIT有關(guān),并且該重排序遵循intra-thread semantics法則(重排序后不會影響單線程的執(zhí)行結(jié)果)。

如果發(fā)生重排序,第3步先于第2步執(zhí)行,那么A線程可能只是讓對象指向內(nèi)存地址,并沒有實質(zhì)的初始化對象,那么線程B調(diào)用時就會發(fā)生錯誤。

解決方案

  • 采用volatile

在Java1.5以后,volatile關(guān)鍵字被加強,這種重排序不允許在多線程中發(fā)生。
即在對象聲明加上volatile關(guān)鍵字。
實質(zhì):禁止編譯的重排序。

  • 采用JVM初始化類時加鎖

JVM的類的初始化階段,會獲取鎖,該鎖可以同步多線程對一個類的初始化。
此時衍生一種稱為:Initialization On Demand Holder idiom的解決方案。

    //-----------------------------靜態(tài)內(nèi)部類---------------
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance3() {
        return SingletonHolder.INSTANCE;
    }
JVM在多線程中初始化對象過程.jpg

** 實質(zhì): **利用JVM的多線程初始化對象的特性,允許重排序,但對其他線程不可見。

另外根據(jù)Java語言規(guī)范,一個類在一下5種情況會發(fā)生初始化:
  • T是一個類,并且T的實例被創(chuàng)建。
  • T是一個類,且T中的靜態(tài)方法被調(diào)用
  • T是一個類,且T中的一個靜態(tài)字段被賦值。
  • T是一個類,且T中的非常量字段被使用
  • T是一個頂級類(TOP Level Class),有斷言語句嵌套在T內(nèi)部被執(zhí)行。(assert語句,很少用改規(guī)則)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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