類的加載時機彙總

實踐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() 方法返回。線程

Q:INSTANCE 在建立過程當中又是如何保證線程安全的呢?

《深刻理解JVM》引用:code

JVM 保證一個類的 <clinit>() 方法在多線程環境中被正確地加鎖、同步。

若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>() 方法,
其餘線程都須要阻塞等待,直到活動線程執行完 <clinit>() 方法。

當某個線程第一次執行了<clinit>() 這個初始化方法以後,其餘線程就不會重複執行初始化了。

同一個加載器下,一個類只會被初始化一次。
複製代碼

所以,一個靜態內部類被加載初始化時,從類加載器的角度來看,是線程安全的,而單例對象 INSTANCE 在這個靜態內部類中是一個靜態 Field,因此它跟隨這個靜態內部類的加載而初始化,且只在類加載時初始化一次。對象

靜態內部類初始化的缺陷

傳參問題:ip

因爲是靜態內部類的形式去建立單例的,因此外部沒法傳遞參數進去,如 Context 這種參數。get

所以,在建立單例時,能夠在靜態內部類與 DCL 模式之間權衡選擇。

相關文章
相關標籤/搜索