Java之單例模式的各種實現
阿新 • • 發佈:2019-02-08
最近連續在各種群裡、部落格裡看到單例模式的討論。根據我的理解總結一下:
先直接說結論:最優雅最簡潔最穩的方法是使用列舉實現單例模式。
餓漢式
//無懶載入
//在類載入時初始化唯一的例項物件,由jvm在多執行緒環境時保證執行緒安全
//增加了初始化的時間和記憶體開銷
public class SingleDog {
private static final SingleDog instance = new SingleDog();
private SingleDog(){}
public SingleDog getInstance(){
return instance;
}
}
懶漢式
//懶載入
//因為例項化在getInstance裡執行,所以每次訪問instance,都需要獲取同步鎖,比較笨重;
public class SingleDog {
private static SingleDog instance = null;
private SingleDog(){}
synchronized public static SingleDog getInstance(){
//判斷是否需要對Instance進行初始化
if(instance == null){
instance = new SingleDog();
}
return instance;
}
懶漢式多執行緒環境下優化 之 雙重檢查鎖定
//懶載入(雙重檢查鎖定)
//多執行緒訪問時,只在可能需要對Instance進行初始化時獲取鎖,大多數時候直接讀取Instance即可
public class SingleDog {
//需用volatile關鍵字保證對這個變數所做的操作是所有執行緒可見的
private volatile static SingleDog instance = null;
private SingleDog(){}
public static SingleDog getInstance(){
//判斷是否需要對Instance進行初始化
if(instance == null){
synchronized(SingleDog.class){
//對於已經獲取到鎖的其他執行緒,再次判斷Instance是否需要進行初始化
if(instance == null){
instance = new SingleDog();
}
}
}
return instance;
}
//雙重檢查鎖定也不能避免使用重度鎖synchronized,在獲取和釋放鎖的過程中會有效能損耗;同時需要兩個if判斷來確保只有一個例項,程式邏輯比較複雜
懶漢式多執行緒環境下優化 之 靜態內部類實現
//與第一個餓漢式相比,使用一個私有靜態內部類來代替SingleDog類,持有instance例項,達到了懶載入的目的。
//擺脫了重度鎖synchronized
//那麼多執行緒環境下一個類是否會被初始化多次呢?
//jvm在類載入時會確保執行緒的安全,如果多個執行緒去初始化一個類,只會有第一個執行緒被執行,其他執行緒都會被阻塞而且不會再次進入到類的初始化中去。同一個類載入器下,一個類只會被初始化一次
public class SingleDog {
private SingleDog() {}
private static class SingleHolder{
private static SingleDog instance = new SingleDog();
}
public static SingleDog getInstance(){
return SingleHolder.instance;
}
}
有什麼辦法破壞上述單例模式
反射
雖然建構函式已經被限定為private了,但是隻要有建構函式存在,就可以通過Java反射機制獲取到它,還能強制性的setAccessble,將其設定為可訪問,從而利用建構函式再生成一個新的例項,破壞單例模式。部分程式碼如下:
SingleDog s1 = SingleDog.getInstance();
Class<SingleDog> cls = (Class<SingleDog>) s1.getClass();
Constructor<SingleDog> cons = cls.getDeclaredConstructor(new Class[] {});
cons .setAccessible(true);
SingleDog s2 = cons.newInstance(new Object[] {});
System.out.println(s1 == s2);//false
反序列化
當將Java物件序列化後,再反序列時,readObject方法會自動建立一個新的例項。天然破壞了被序列化物件的單例模式。
不過這個問題很好解決,只需要在類中新增一個方法就行:
public class SingleDog {
private SingleDog() {}
private static class SingleHolder{
private static SingleDog instance = new SingleDog();
}
public static SingleDog getInstance(){
return SingleHolder.instance;
}
//劃重點!
public Object readResolve() {
return instance;
}
}
列舉實現單例模式
//列舉實現單例模式
//防反射、防反序列化、執行緒安全(例項在類初始化期間就已經建立)
//列舉類實際上是繼承於java.lang.Enum的一個抽象類,所以是無法用反射獲取到其構造方法的
enum SingleDog {
INSTANCE;
public static SingleDog getInstance() {
return INSTANCE;
}
}