單例模式以及七種實現方式

作者:KaelQ
鏈接:https://www.jianshu.com/p/ad86f107425c
單例模式以及七種實現方式

1.定義

  • 一個類只有一個實例,並且只有一個全局獲取入口。

2.適用場景

  • 某個實例對象頻繁被訪問。
  • 某個實例佔用的資源較多。

3.實現方式

3.1 懶漢模式(線程不安全)

這樣可能會出現線程不同的方法,所以必須對getSingleton方法進行同步。

public class Singleton {
private static Singleton singleton;//私有靜態變量
private Singleton(){};//私有構造方法
//全局靜態訪問入口
public static Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}

3.2 懶漢模式(線程安全)

public class Singleton { 

private static Singleton singleton;//私有靜態變量
private Singleton(){};//私有構造方法
//全局靜態訪問入口
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}

這樣子線程就安全了,但是消耗了不必要的同步資源,不推薦這樣使用。

3.3 餓漢模式(線程安全)

public class Singleton {
private static Singleton singleton=new Singleton();//私有靜態變量
private Singleton(){};//私有構造方法
//全局訪問入口
public Singleton getSingleton(){
return singleton;
}
}

避免線程安全問題,在聲明私有靜態變量時,就已經實例化了類,不用考慮線程同步問題。

3.4 DCL模式(Double CheckLock)

public class Singleton {
private static Singleton singleton;//私有靜態內部變量
private Singleton(){};//私有構造方法
//全局訪問入口
public static synchronized Singleton getSingleton(){
if(singleton==null){

synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}

通過兩個判斷,第一層是避免不必要的同步,第二層判斷是否為null。

可能會出現DCL模式失效的情況。

DCL模式失效:

singleton=new Singleton();這句話執行的時候,會進行下列三個過程:

  1. 分配內存。
  2. 初始化構造函數和成員變量。
  3. 將對象指向分配的空間。

由於JMM(Java Memory Model)的規定,可能會出現1-2-3和1-3-2兩種情況。

所以,就會出現線程A進行到1-3時,就被線程B取走,然後就出現了錯誤。這個時候DCL模式就失效了。

Sun官方注意到了這種問題,於是就在JDK1.5之後,具體化了volatile

關鍵字,這時候只用調整一行代碼即可。

private volatile static Singleton singleton;

3.5 靜態內部類模式

public class Singleton {
private Singleton(){}
public static Singleton getSingleton(){
return SingletonHolder.singleton;
}
private static class SingletonHolder{
private final static Singleton singleton=new Singleton();
}
}

第一次加載Singleton類不會加載SingletonHolder類,但是調用getSingleton時,才會加載SingletonHolder,才會初始化singleton。即確保了線程安全,又保證了單例對象的唯一性,延遲了單例的實例化。這是最推薦的方式。

3.6 枚舉模式

public enum Singleton{
singleton;
public void hello(){
System.out.print("hello");
}
}

這樣很簡單,線程時安全的,並且避免了序列化和反射攻擊。

除了枚舉模式,其他模式在實現了Serializable接口後,反序列化時單例會被破壞。所以要重寫readResolve()方法。

private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}

3.7 使用容器實現單例模式

public class SingletonManger{
private static Map objectMap=new HashMap();
private SingletonManger(){}
public static void registerService(String key,Object singleton){
if(!objectMap.containsKey(key)){
objectMap.put(key,singleton);
}
}
public static Object getObjectService(String key){
return objectMap.get(key);
}
}

這樣可以將多個單例對象注入到HashMap中,進行統一管理,更加方便快捷。


分享到:


相關文章: