Singleton
所謂單例,指的就是單實例,有且僅有一個類實例。
運用場景很多,例如網站的在線人數,window系統的任務管理器,網站計數器等等,這些都是單例模式的運用。單例模式有常見的8種形式,如下:
1.Lazy1【不可用】
-
懶漢式1:
-
線程不穩定
-
延遲初始化
-
多線程不安全
-
是最基本的實現方式,不支持多線程,因爲沒有synchronized加鎖,多線程不能工作。
-
實現圖
-
多線程則會出現,當Singleton_Lazy1類剛剛被初始化,instance對象還是空,這時候兩個線程同時訪問到getInstance方法,因爲Instance是空,所以A\B兩個線程都通過了instance爲空的判斷,則A\B兩個線程都會實例化對象,單例失敗。
不加同步的懶漢式是線程不安全的,如下示例:
-
2.Lazy2(同步方法)【不建議使用】
-
懶漢式2:
-
線程穩定
-
延遲初始化
-
多線程安全
-
優點:調用才初始化,避免內存浪費。
-
缺點:必須加鎖synchronized才能保證單例,但每次調用都要加鎖會影響效率。
-
實現圖
-
利用synchronized關鍵字對getInstance方法加鎖使得多線程安全且穩定但效率不高。
多線程實現方法,如下圖所示:
-
3.DCL1(同步代碼塊)【不可用】
-
雙重檢測機制:
-
延遲初始化
-
多線程不安全
-
線程穩定
-
1.爲了防止new Singleton被執行多次,因此在new操作之前加上Synchronized 同步鎖,鎖住整個類(注意,這裏不能使用對象鎖)。
-
2.進入Synchronized 臨界區以後,還要再做一次判空。因爲當兩個線程同時訪問的時候,線程A構建完對象,線程B也已經通過了最初的判空驗證,不做第二次判空的話,線程B還是會再次構建instance對象。這種同步並不能起到線程同步的作用
private Singleton() { } //私有構造函數 private static Singleton instance = null; //單例對象 public static Singleton getInstance() { if (instance == null) { //雙重檢測機制 synchronized (Singleton.class){ //同步鎖 if (instance == null) { //雙重檢測機制 instance = new Singleton(); } } } return instance; }
-
但是這樣的雙重檢測機制仍然不是絕對線程安全!這裏涉及到JVM編譯器的指令重排。
一般創建一個對象的時候會有三個步驟:
instance = new Singleton Value =allocate(); //1:分配對象的內存空間 ctorInstance(Value); //2:初始化對象 instance =Value; //3:設置instance指向剛分配的內存地址
但這三步並不是固定不變的,有可能會經過JVM和CPU的優化,指令將會重排爲:
instance = new Singleton Value =allocate(); //1:分配對象的內存空間 instance =Value; //3:設置instance指向剛分配的內存地址 ctorInstance(Value); //2:初始化對象
當線程A執行完1、3時,instance對象還未完成初始化,但是已經不知向null。這個時候如果B線程進入CPU,則會搶先執行if(instance == null)的判斷結果爲false,這個時候getInstance方法則會返回一個沒有初始化完成的instance對象,結果如下圖:
-
4.DCLPro【推薦使用】
- 雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking):
-
線程穩定
-
延遲初始化
-
多線程安全
-
volatile關鍵字是防止創建對象時的重排序,在訪問volatile變量時不會執行加鎖操作。
volatile關鍵字不但可以防止指令重排,也可以保證線程訪問的變量值是主內存中的最新值。
-
完美解決3.Lazy3的問題。
-
5.Hunger1(靜態常量)【可用】
- 餓漢式(靜態常量):
-
線程穩定
-
不會延遲加載
-
多線程安全
-
優點:採用了類裝載的機制來保證初始化實例時只有一個線程,避免了線程同步問題。沒有用synchronized進行加鎖,提高執行效率。
-
缺點:在類裝載的時候就完成實例化,沒有達到延遲加載的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費。
-
6.Hunger2(靜態代碼塊)【可用】
- 餓漢式(靜態代碼塊):
-
線程穩定
-
不會延遲初始化
-
多線程安全
-
優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。沒有用synchronized進行加鎖,提高執行效率。
-
缺點:在類裝載的時候就完成實例化,沒有達到延遲加載的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費。
-
這種方式和上面的方式其實類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候,就執行靜態代碼塊中的代碼,初始化類的實例。
static{ instance = new Singleton_Hunger2; }
-
7.Pattern(靜態內部類)【推薦使用】
- 靜態內部類:
-
線程穩定
-
延遲加載
-
多線程安全
-
優點:和5.Hunger1類似,採用了類裝載的機制來保證初始化實例時只有一個線程。
但靜態內部類則可以達到延遲加載的效果,在Singleton_Pattern類被裝載的時候並不會馬上實例化,而是在需要實例化的時候再調用getInstance方法實例化,這樣纔會裝載靜態內部類SingletonInstance,從而達到Singleton_Pattern的實例化。 類的靜態屬性只會在第一次加載類的時候初始化,如此在類初始化的時候其他進程是無法進入的,從而保護了線程的安全。 -
總結爲:避免了線程不安全,延遲加載,效率高。
-
8.Enum【推薦使用】
不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。代碼簡潔。使用起來方便。
9.總結
1.Singleton_Lazy1:
public class Singleton_Lazy1 {
private Singleton_Lazy1() {
};
private static Singleton_Lazy1 instance = null;
public static Singleton_Lazy1 getInstance() {
if (instance == null) {
instance = new Singleton_Lazy1();
}
return instance;
}
}
2.Singleton_Lazy2:
public class Singleton_Lazy2 {
private Singleton_Lazy2() {
};
private static Singleton_Lazy2 instance = null;
public static synchronized Singleton_Lazy2 getInstance() {
if (instance == null) {
instance = new Singleton_Lazy2();
}
return instance;
}
}
3.Singleton_DCL1:
public class Singleton_DCL1 {
private Singleton_DCL1() {
};
private static Singleton_DCL1 singleton;
public static Singleton_DCL1 getInstance() {
if (singleton == null) {
synchronized (Singleton_DCL1.class) {
singleton = new Singleton_DCL1();
}
}
return singleton;
}
}
4.Singleton_DCLPro:
public class Singleton_DCLPro {
private Singleton_DCLPro() {
};
private volatile static Singleton_DCLPro singleton;
public static Singleton_DCLPro getInstance() {
if (singleton == null) {
synchronized (Singleton_DCLPro.class) {
if (singleton == null) {
singleton = new Singleton_DCLPro();
}
}
}
return singleton;
}
}
5.Singleton_Hunger1:
public class Singleton_Hunger1 {
private final static Singleton_Hunger1 INSTANCE = new Singleton_Hunger1();
private Singleton_Hunger1() {
};
public static Singleton_Hunger1 getInstance() {
return INSTANCE;
}
}
6.Singleton_Hunger2:
public class Singleton_Hunger2 {
private static Singleton_Hunger2 instance;
static {
instance = new Singleton_Hunger2();
}
private Singleton_Hunger2() {
};
public Singleton_Hunger2 getInstance() {
return instance;
}
}
7.Singleton_Pattern:
public class Singleton_Pattern {
private Singleton_Pattern() {
};
private static class SingletonInstance {
private static final Singleton_Pattern INSTANCE = new Singleton_Pattern();
}
public Singleton_Pattern getSingleton() {
return SingletonInstance.INSTANCE;
}
}
8.Singleton_E:
public enum Singleton_E {
INSTANCE;
public void whateverMethod() {
System.out.println("這是一個枚舉單例");
}
}
關鍵字
synchronized
synchronized的使用
- 修飾實例方法,對當前實例對象加鎖
- 修飾靜態方法,多當前類的Class對象加鎖
- 修飾代碼塊,對synchronized括號內的對象加鎖
volatile
在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。
volatile關鍵字是防止創建對象時的重排序,在訪問volatile變量時不會執行加鎖操作。
volatile關鍵字不但可以防止指令重排,也可以保證線程訪問的變量值是主內存中的最新值。