1. 程式人生 > 程式設計 >要不要來看看解決重排序問題後的單例模式

要不要來看看解決重排序問題後的單例模式

簡單介紹

單例模式是最簡單的設計模式之一,提供了一種建立物件的方式,確保在整個系統中只有一個物件被建立.單例模式解決了頻繁建立重複物件的問題節約資源,可以省略建立物件所需要花費的時間,對於一些重量級物件而言這點是很重要的.並且因為不需要頻繁建立物件 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 的單例模式還能避免反序列化破壞單例並且不能被反射攻擊.