要不要來看看解決重排序問題後的單例模式
簡單介紹
單例模式是最簡單的設計模式之一,提供了一種建立物件的方式,確保在整個系統中只有一個物件被建立.單例模式解決了頻繁建立重複物件的問題節約資源,可以省略建立物件所需要花費的時間,對於一些重量級物件而言這點是很重要的.並且因為不需要頻繁建立物件 GC 的壓力也會有所減輕.
單例模式的一些實現方式
通常來說在 Java 中的單例模式分為餓漢式和懶漢式,而且單例類需要一個 private 的建構函式防止被其他程式碼例項化.下面來具體說一下java 中單例模式的實現.
餓漢式
public class Singleton{
private static Singleton instance =new Singleton();
//私有化構造方法
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
複製程式碼
餓漢式的單例模式程式碼簡單,執行緒安全.先建立物件,然後等待呼叫首先私有化構造方法,防止別人使用new 建立物件.通過classLoader機制保證了單例物件的唯一性 但是不能確保instance 是在呼叫getInstance()方法的時候生成的不能達到懶載入效果
懶漢式
public class Singleton{
private static Singleton instance;
private Singleton(){}
//加入 synchronize 保證執行緒安全
public synchronized static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
複製程式碼
為了達到懶載入的效果,我們使用懶漢式的單例模式,在第一次呼叫方法getInstance()的時候才去建立物件.可以達到延遲載入的效果並且加入了 synchronize 保證執行緒安全,但每次呼叫程式碼的時候都要加鎖,效能比較低還有可能發生阻塞
DCL雙重校驗鎖
public class Singleton{
//volatile防止指令重排序
private static volatile Singleton instance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
//加入第二次校驗
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
複製程式碼
雙重校驗鎖就是為瞭解決上述問題而存在的,先檢查例項是否存在然後再去建立,可以不用每次呼叫方法都獲取同步鎖效能會有一些提升,減小的鎖的顆粒度.但是 java 物件的建立和賦值不是一步操作的,有可能先去賦值給中instance之後才去建立 Singleton 這時新增volatile關鍵字防止指令重排序解決了這個問題.
物件建立的過程:
在程式碼的第 12 行 instance =new Singleton();大致分為三個過程 1.分配物件的記憶體空間 此時 instance !=null
2.初始化物件
3.將instance 指向分配的記憶體空間
其中2 和 3不一定是有序的 所以執行緒 B 會訪問到一個還未初始化的物件
靜態內部類
public class Singleton{
private Singleton(){}
public static class SingleHoler{
public static final Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingleHoler.instance;
}
}
複製程式碼
看過繁瑣的DCL後 下面介紹一種簡潔的單例模式靜態內部類.當Singleton被建立的時候不會去載入SingleHoler,只有第一次呼叫getInstance()方法時才回去建立instance,載入SingleHoler將常量池中的符號引用替換成直接引用,這種方式不僅保證了執行緒安全而且可以達到延遲載入的效果.
classload機制
解決重排序的方法有兩種,第一種就是使用 volatile,第二種則是現在要介紹的方法
呼叫類的靜態成員(非字串常量)的時候會導致類(SingleHoler)的初始化.並且在執行類的初始化期間,JVM 會獲取一個初始化鎖,這個鎖可以同步多個執行緒對同一個類的初始化.
類載入的步驟:
將符號引用替換成直接引用是在解析的階段完成的.
最佳實踐
public enum Singleton{
INSTANCE;
public void print(){
System.out.println("快樂就完事了!");
}
}
複製程式碼
這種方法在功能上與公有域方法相近,但是它更加簡潔,無償提供了序列化機制,絕對防止多次例項化,即使是在面對複雜序列化或者反射攻擊的時候。雖然這種方法還沒有廣泛採用,但是單元素的列舉型別已經成為實現Singleton的最佳方法。 --《Effective Java 中文版 第二版》
簡單到不能再簡單了啊.jvm 在載入列舉類的時候會使用loadClass方法使用同步程式碼塊解決執行緒安全問題.使用 enum 的單例模式還能避免反序列化破壞單例並且不能被反射攻擊.