實踐Tip:經過單例模式引入JVM類加載機制的內容。java
首先,須要瞭解類的加載時機。安全
Java 虛擬機在有且僅有的 5 中場景下會對類進行初始化。bash
一、遇到 new、getstatic、setstatic 或 invokestatic 這 4 個字節碼指令時,分別對應以下Java代碼場景:
new : new一個實例化對象
getstatic 讀取一個靜態字段(final修飾、已在編譯期把結果放入常量池的除外)
setstatic 設置一個靜態字段(同上)
invokestatic 調用一個類的靜態方法
二、使用 java.lang.reflect 包中的方法,對類進行反射調用時,若是類沒有初始化過,會觸發初始化之。
三、當初始化一個類時,若是父類未初始化,會先觸發父類的初始化。
四、當虛擬機啓動時,用戶須要制定一個要執行的主類(含 main 方法的類),虛擬機會先初始化這個類。
五、當使用JDK 1.7等動態語言支持時,若是一個java.lang.invoke.MethodHandle
實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,
而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。
複製代碼
以上5中狀況被稱爲類的主動引用。除此以外的全部對類的引用都不會對類進行初始化,被成爲被動引用。多線程
靜態內部類就是被動引用的類型。spa
當 getInstance() 方法被調用時,SingletonHolder 纔在 Singleton 的運行時常量池裏,把符號引用替換爲直接引用,這時靜態對象 INSTANCE 也才最終被建立,而後再被 getInstance() 方法返回。線程
《深刻理解JVM》引用:code
JVM 保證一個類的 <clinit>() 方法在多線程環境中被正確地加鎖、同步。
若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>() 方法,
其餘線程都須要阻塞等待,直到活動線程執行完 <clinit>() 方法。
當某個線程第一次執行了<clinit>() 這個初始化方法以後,其餘線程就不會重複執行初始化了。
同一個加載器下,一個類只會被初始化一次。
複製代碼
所以,一個靜態內部類被加載初始化時,從類加載器的角度來看,是線程安全的,而單例對象 INSTANCE 在這個靜態內部類中是一個靜態 Field,因此它跟隨這個靜態內部類的加載而初始化,且只在類加載時初始化一次。對象
傳參問題:ip
因爲是靜態內部類的形式去建立單例的,因此外部沒法傳遞參數進去,如 Context 這種參數。get
所以,在建立單例時,能夠在靜態內部類與 DCL 模式之間權衡選擇。