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關鍵字不但可以防止指令重排,也可以保證執行緒訪問的變數值是主記憶體中的最新值,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/255151.html
標籤:其他
