餓漢模式
public class Singleton1 implements Serializable {
private static Singleton1 instance = new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return instance;
}
}
是怎麼保證線程安全的?
線程安全的懶漢模式
雙重檢測鎖(Double Checked Lock DCL) + volatitle
- DCL,避免多個線程同時指執行到if(instance == null),判斷都爲true,會排隊創新等待創建新對象
- volatitle關鍵字 防止CPU指令重排,instance = new Singleton4()不是原子操作,會涉及三個步驟,CPU執行順序有可能會亂(給實例分配內存空間;調用對象的構造方法,初始化成員字段;將instance 對象指向分配的內存空間)
public class Singleton4 {
private volatile static Singleton4 instance;
private Singleton4(){}
public static Singleton4 getInstance(){
if(instance == null){
synchronized (Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
}
在第一次調用Singleton4.getInstance()的時候初始化靜態變量
類並不是在加載完之後就會實例static變量。那到底什麼時候纔會初始化呢?
1.當遇到new,getstatic,putstatic或者invokestatic這四條字節碼指令的時候,如果該類沒有進行
初始化,則需要先初始化.這四條指令對應的是實例化對象,獲取一個靜態變量,設置一個靜態變量(常量
放在常量池中,不會觸發),或者調用靜態方法的時候.
2.當時候反射包的方法對類進行反射調用的時候
3.當初始化一個類的時候,發現該類的父類還沒有進行初始化,則初始其父類
4.當jvm啓動的時候,當用戶指定執行一個主類(就是包含main的那個類),虛擬機會先初始化這個類.
上述的例子是因爲滿足第一條,執行static方法的時候編譯器會生成invokestatic指令,這時候instance沒有初始化,所以會執行Instance的構造方法,然後在return返回。
靜態內部類模式
public class Instance1 {
private static class Holder{
private static Instance1 instance = new Instance1();
}
private Instance1(){
System.out.println("instance1 alloc");
}
public static Instance1 getInstance(){
System.out.println("instance1 called");
return instance;
}
}
靜態內部類和餓漢模式都採用類裝載的機制保證,當初始化實例的時候只有一個線程執行,保證了多線程下的安全。
JVM會在類初始化的階段(類裝載的階段)創建一個鎖,該鎖保證多個線程同步執行類初始化的工作,因此在多線程環境下,類加載的機制依然是安全的。
餓漢模式 是啓動就加載,造成資源浪費
靜態內部類 只有在調用getInstance方法的時候 纔會去裝載內部類,從而完成實例的初始化,不造成資源的浪費。 爲比較推薦的單例實現方式。