從單例模式到類的初始化順序

讀完本文將了解到

  • 單例模式的常用雙重檢查鎖寫法,以及如何避免JVM指令重排序?qū)е碌氖?/li>
  • 更加高效、安全的內(nèi)部靜態(tài)類延時(shí)初始化的單例模式寫法
  • 類的初始化順序(靜態(tài)屬性、靜態(tài)代碼塊、普通屬性、普通代碼塊、子類、父類...)

引子

我們都知道常見的單例模式的雙重檢查鎖的形式,代碼如下:

public class Singleton{
    private static volatile Singleton instance;                (4)
    private Singleton(){}                                     
    public static Singleton getInstance(){
        if(instance == null){                                   (1)
            synchronized(Singleton.class){                      (2)
                if(instance == null){                           (3)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

其中通過(1)(3)作為雙重檢查,而(2)加鎖保證初始化是線程安全的,
(4)中使用volatile關(guān)鍵字保證了內(nèi)存可見性,防止指令重排序?qū)е码p重檢查鎖失效。

雙重檢查鎖失效的原因是由于instance = new instance()不是原子性操作,從而留給了JVM重排序的機(jī)會(huì)。在單線程中,不管怎么排,最終結(jié)果都一致;在多線程情況下指令就會(huì)發(fā)生重排序出現(xiàn)問題。具體參考: 雙重檢查鎖失效分析

更高效、安全的單例模式實(shí)現(xiàn)

在了解類初始化機(jī)制后可以采用更加高效、安全的線程安全單例模式,即內(nèi)部靜態(tài)類做延時(shí)初始化,代碼如下:

public class Singleton{
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();(3)
    }
    private Singleton(){}
    public static Singleton getInstance(){                        (1)
        return SingletonHolder.INSTANCE;                          (2)
    }
}

其執(zhí)行過程如下:
(1)執(zhí)行Singleton.getInstance(),檢測(cè)Singleton.class是否已被載入,若尚未被載入,則開始載入Singleton.class文件,并完成相應(yīng)的靜態(tài)初始化動(dòng)作;
(2)執(zhí)行SingletonHolder.INSTANCE,檢測(cè)發(fā)現(xiàn)SingletonHolder.class尚未被載入,則開始載入SingletonHolder.class文件,并執(zhí)行相應(yīng)的靜態(tài)初始化動(dòng)作;
(3)SingleHolder.class中靜態(tài)成員INSTANCE初始化動(dòng)作會(huì)執(zhí)行new Singleton()方法,完成了對(duì)Singleton實(shí)例INSTANCE的初始化操作后返回改實(shí)例。

類的初始化

類的初始化順序 //todo 流程圖完善

(1)類加載器檢查該類的.class文件是否已被載入,若沒有則載入;
(2)初始化靜態(tài)屬性、靜態(tài)代碼塊(按代碼順序);
(3)初始化普通屬性、普通代碼塊(按代碼順序);
(4)調(diào)用構(gòu)造函數(shù);
(5)在(1)、(2)、(3)、(4)的過程中若發(fā)現(xiàn)該類存在父類,并且其父類的.class尚未被載入如則先載入父類的.class文件,并執(zhí)行父類的(2)、(3)、(4)、(5)步驟。

觸發(fā)類初始化行為

(1)在使用new實(shí)例化對(duì)象,訪問靜態(tài)數(shù)據(jù)和方法時(shí),也就是遇到指令:new,getstatic/putstatic和invokestatic時(shí);
(2)使用反射對(duì)類進(jìn)行調(diào)用時(shí);
(3)當(dāng)初始化一個(gè)類時(shí),父類如果沒有進(jìn)行初始化,先觸發(fā)父類的初始化;
(4)執(zhí)行入口main方法所在的類;
(5)JDK1.7動(dòng)態(tài)語(yǔ)言支持中方法句柄所在的類,如果沒有初始化觸發(fā)起初始化;

測(cè)試代碼

class Parent{   
    private static String str1 = "父類靜態(tài)屬性1";   
    private String string = "父類普通屬性";   
    static {      
        System.out.println(str1);  
        //error-illegal forward reference    
        System.out.println(str2);                                 (1)
        //ok-not accessed via a simple name
        System.out.println(Parent.str2);                          (2)
        System.out.println("父類靜態(tài)代碼塊");   
    }   
    {      
        System.out.println(string);      
        System.out.println("父類普通代碼塊");   
    }   
    public static String str2 = "父類靜態(tài)屬性2";   
    public Parent() {      
        System.out.println("父類構(gòu)造函數(shù)");   
    }
}
class Child extends Parent{   
    private static String str = "子類靜態(tài)屬性";   
    private String string = "子類普通屬性";   
    static {      
        System.out.println(str);      
        System.out.println("子類靜態(tài)代碼塊");   
    }   
    {      
        System.out.println(string);      
        System.out.println("子類普通代碼塊");   
    }   
    public Child() {      
        System.out.println("子類構(gòu)造函數(shù)");   
    }
}
class Child2 extends Parent{   
    private static String str = "子類2靜態(tài)屬性";   
    private String string = "子類2普通屬性";   
    static {      
        System.out.println(str);      
        System.out.println("子類2靜態(tài)代碼塊");   
    }   
    {      
        System.out.println(string);      
        System.out.println("子類2普通代碼塊");   
    }   
    public Child() {      
        System.out.println("子類2構(gòu)造函數(shù)");   
    }
}
public class MainTest {   
    static {      
        System.out.println("MainTest靜態(tài)代碼塊");   
    }   
    public static void main(String[] args){    
        System.out.println("main() executed");  
        new Child();   
    }
}

輸出結(jié)果

MainTest靜態(tài)代碼塊
main() executed
父類靜態(tài)屬性1
null
父類靜態(tài)代碼塊
子類靜態(tài)屬性
子類靜態(tài)代碼塊
父類普通屬性
父類普通代碼塊
父類構(gòu)造函數(shù)
子類普通屬性
子類普通代碼塊
子類構(gòu)造函數(shù)
子類2靜態(tài)屬性
子類2靜態(tài)代碼塊
父類普通屬性
父類普通代碼塊
父類構(gòu)造函數(shù)
子類2普通屬性
子類2普通代碼塊
子類2構(gòu)造函數(shù)

結(jié)果分析

結(jié)果與前面描述的類初始化順序結(jié)論一致,有幾點(diǎn)需要注意:
1、靜態(tài)屬性和靜態(tài)代碼塊是按照統(tǒng)一的順序執(zhí)行的。
2、在代碼(1)之所以編譯器會(huì)報(bào)“illegal forward reference”是因?yàn)樵诼暶鱯tr2之前就進(jìn)行了read操作,要想強(qiáng)制使用,必須使用完成的類名+變量名。如果使用類名+變量名,這種向前引用就是null,如(2)所示。
3、更多詳細(xì)參考illegal forward reference的介紹在The Java Language Specification的Forward References During Field Initialization

總結(jié)

1、傳統(tǒng)的雙重檢查鎖單例模式有時(shí)會(huì)由于在多線程情況下JVM的指令重排序?qū)е率У那闆r,要避免這種情況,需要給instance加上volatile關(guān)鍵字保證內(nèi)存可見性,防止指令重排序。
2、為了更高效安全的實(shí)現(xiàn)單例模式,采用內(nèi)部靜態(tài)類做延時(shí)初始化方法,由JVM的類加載初始化機(jī)制幫我們實(shí)現(xiàn)線程安全的單例模式。
3、為了加深對(duì)類初始化順序的理解,先介紹了類的初始化順序偽代碼,并在后文編寫代碼進(jìn)行了結(jié)果驗(yàn)證和分析。

最后編輯于
?著作權(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)容