兩種正確的延遲初始化加載方案

一.雙重檢查鎖定

不安全的延遲初始化方案:

public class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();// 8問題發(fā)生的根源
                }
            }
        }
        return instance;
    }
}

第八行創(chuàng)建對(duì)象,在CPU指令層面可能分為三個(gè)步驟:
memory = allocate();//1分配對(duì)象內(nèi)存空間
ctorInstance(memory);//2初始化對(duì)象
instance = memory;//3設(shè)置instance指向更分配的內(nèi)存地址
這幾個(gè)步驟的執(zhí)行時(shí)序在單線程情況下可能如下:



而在多線程的情況下,可能如下:



這種延遲初始化方案缺陷的解決方案很簡(jiǎn)單,只要在instance變量前面加上volatile修飾符,即禁止2、3指令的重排,前面描述的問題即可解決。
public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();// 7問題發(fā)生的根源
                }
            }
        }
        return instance;
    }
}

二.基于類初始化的解決方案

public class Singleton {
    private Singleton(){}
    private static class SingletonHolder{
        public static Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

根據(jù)java語言規(guī)范,在以下幾種情況下,類會(huì)被初始化:
1)T是一個(gè)類,而且一個(gè)T類型的實(shí)例被創(chuàng)建。
2)T是一個(gè)類,且T中聲明的一個(gè)靜態(tài)方法被調(diào)用。
3)T中聲明的一個(gè)靜態(tài)字段被賦值。
4)T中聲明的一個(gè)靜態(tài)字段被使用,而且這個(gè)字段不是一個(gè)常量字段。
5)T是一個(gè)頂級(jí)類(Top Level Class,見Java語言規(guī)范的§7.6),而且一個(gè)斷言語句嵌套在T
內(nèi)部被執(zhí)行。
在Singleton示例中,首次執(zhí)行g(shù)etInstance方法(會(huì)觸發(fā)情況4)的線程將導(dǎo)致SingletonHolder類被初始化。
該延遲加載方案是基于JVM的類初始化原理實(shí)現(xiàn)的Instance實(shí)例化一次的單例。在執(zhí)行累的初始化期間,JVM回去獲取一個(gè)鎖,該鎖可以同步多個(gè)線程對(duì)同一個(gè)類的初始化。
JVM對(duì)類的初始化原理如下:
對(duì)于每一個(gè)類或接口C,都有一個(gè)初始化鎖LC。至于C和LC的映射方式則由JVM實(shí)現(xiàn)靈活定制,JVM在類初始化期間會(huì)獲取這個(gè)初始化鎖,并且每個(gè)線程至少獲取一次鎖來確保這個(gè)類已經(jīng)被初始化過了。因此,C的初始化過程如下:
1,等待并嘗試獲取C的初始化鎖LC
2,如果C的Class對(duì)象表明其他線程正在進(jìn)行C的初始化,那么釋放LC,并阻塞當(dāng)前線程,直到其他線程完成初始化過程。
3,如果C的Class對(duì)象表明當(dāng)前線程正在進(jìn)行C的初始化,那么直接完成初始化過程并釋放之前獲取的鎖LC。
4,如果C的Class對(duì)象表明C已經(jīng)被初始化完成,那么不需要其他操作,釋放鎖LC。
5,如果C的Class對(duì)象處于失敗狀態(tài),那么無法完成初始化,釋放鎖LC,拋出NoClassDefFoundError。
6,否則,記錄下當(dāng)前線程正在執(zhí)行C的Class對(duì)象的初始化過程,并釋放鎖LC。然后按照Class文件中ConstantValue屬性表中的順序,初始化所有final static屬性。
7,接下來,如果C是個(gè)類而不是接口,并且它的父類還沒有被初始化,那么先遞歸的初始化他的父類,如果必要,先執(zhí)行驗(yàn)證,準(zhǔn)備。
如果父類的初始化過程中,因?yàn)楫惓M蝗恢袛?,然后獲得鎖LC,將C的Class對(duì)象標(biāo)記為失敗狀態(tài),通知所有等待的線程,釋放鎖LC,拋出異常。
8,再接下來,通過查詢C的定義類加載器來判斷C的斷言是否可用。
9,然后執(zhí)行類或接口的初始化方法。
10,如果類或接口的初始化方法執(zhí)行正常完成,那么獲得鎖LC,將C的Class對(duì)象標(biāo)記為初始化完成,通知其他線程,釋放鎖LC,然后正常結(jié)束該過程。
11,否則,方法執(zhí)行中拋出異常E,如果E不是Error或者它的的子類,那么創(chuàng)建ExceptionInInitializerError,并將E作為它的參數(shù)。
12,獲取鎖LC,將C的Class對(duì)象標(biāo)記為失敗狀態(tài),通知其他等待線程,釋放鎖LC,然后拋出異常,中斷該過程。

兩個(gè)線程并發(fā)調(diào)用Singleton.getInstacne的示意圖:



字段延遲初始化降低了類初始化或創(chuàng)建實(shí)例的開銷,但增加了訪問被延遲初始化字段的開銷。如字段確實(shí)需要延遲初始化,如果是需對(duì)實(shí)例字段使用延遲初始化,可用基于volatile的延遲初始化方案;如果需對(duì)靜態(tài)字段使用線程安全的延遲初始化,可使用基于類初始化的方案。

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

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

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