作者:KaelQ
鏈接:https://www.jianshu.com/p/ad86f107425c
![單例模式以及七種實現方式](http://p2.ttnews.xyz/loading.gif)
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();這句話執行的時候,會進行下列三個過程:
- 分配內存。
- 初始化構造函數和成員變量。
- 將對象指向分配的空間。
由於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 MapobjectMap=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中,進行統一管理,更加方便快捷。
閱讀更多 javafirst 的文章