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

簡單介紹

單例模式是最簡單的設計模式之一,提供了一種建立對象的方式,確保在整個系統中只有一個對象被建立.單例模式解決了頻繁建立重複對象的問題節約資源,能夠省略建立對象所須要花費的時間,對於一些重量級對象而言這點是很重要的.而且由於不須要頻繁建立對象 GC 的壓力也會有所減輕.java

單例模式的一些實現方式

一般來講在 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 保證線程安全,但每次調用代碼的時候都要加鎖,性能比較低還有可能發生阻塞jvm

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 !=nullspa

2.初始化對象線程

3.將instance 指向分配的內存空間設計

其中2 和 3不必定是有序的 因此線程 B 會訪問到一個還未初始化的對象code

靜態內部類

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 的單例模式還能避免反序列化破壞單例而且不能被反射攻擊.

相關文章
相關標籤/搜索