單例模式全形式

① 懶漢式:經典寫法,線程不安全
 
public class Singleton {
    private static Singleton instance;    //私有靜態自身類屬性,因爲靜態方法只能訪問靜態成員,所以是靜態的
    private Singleton (){}                //私有構造器
 
    public static Singleton getInstance() {  //給外界提供單例類對象的靜態函數函數:因爲使用第一次從單例類中獲取單例對象的時候,沒有初始化當前類對象,所以只能經過類調用靜態方法的形式
     if (instance == null) {
        instance = new Singleton();
     }
     return instance;
    }
}
     
  之因此稱爲懶漢式,是由於當調用靜態方法getInstance()的時候纔會生成這個單例對象。
 
②懶漢式:線程安全的
 
public class Singleton {
    private static Singleton instance;    //私有靜態自身類屬性,因爲靜態方法只能訪問靜態成員,所以是靜態的
    private Singleton (){}                //私有構造器
 
    public static synchronized Singleton getInstance() {  //給外界提供單例類對象的靜態函數函數:因爲使用第一次從單例類中獲取單例對象的時候,沒有初始化當前類對象,所以只能經過類調用靜態方法的形式
      if (instance == null) {
        instance = new Singleton();
    }
    return instance;
    }
}
     
  能夠說這個方式是爲了處理懶漢式遇到的線程安全問題而想到的一個最偷懶的改進方式,這個方式確實避免了線程安全的問題,可是同時帶來的確實是性能的問題, 同一個函數,在同步操做以後性能多是相差千百倍的。
 
③雙重檢查機制
 
public class Singleton {
    private static Singleton instance;    //私有靜態自身類屬性,因爲靜態方法只能訪問靜態成員,所以是靜態的
    private Singleton (){}                //私有構造器
 
    public static synchronized Singleton getInstance() {  //給外界提供單例類對象的靜態函數函數:因爲使用第一次從單例類中獲取單例對象的時候,沒有初始化當前類對象,所以只能經過類調用靜態方法的形式
        if (instance == null) {                        //Single Checked
             synchronized (Singleton.class) {
                 if (instance == null) {                //Double Checked 第二次檢查的緣由:有可能多個線程同時經過了第一個檢查,可是遇到了同步操做,其中一個線程獲取了操做權,所以必須再一次判斷,不然後面的線程會再次建立一個對象
                     instance = new Singleton();
                 }
             }
         }
         return instance ;
    }  
}

 

這段代碼看起來很完美,很惋惜,它是有問題。主要在於instance = new Singleton()這句,這並不是是一個原子操做,事實上在 JVM 中這句話大概作了下面 3 件事情。spring

  1. 給 instance 分配內存
  2. 調用 Singleton 的構造函數來初始化成員變量
  3. 將instance對象指向分配的內存空間(執行完這步 instance 就爲非 null 了)

可是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3 也多是 1-3-2。若是是後者,則在 3 執行完畢、2 未執行以前,被線程二搶佔了,這時 instance 已是非 null 了(但卻沒有初始化),因此線程二會直接返回 instance,而後使用,而後瓜熟蒂落地報錯。安全

咱們只須要將 instance 變量聲明成 volatile 就能夠了。
 
public class Singleton {
    private volatile static Singleton instance; //聲明成 volatile
    private Singleton (){}

    public static Singleton getSingleton() {
        if (instance == null) {                       
            synchronized (Singleton.class) {
                if (instance == null) {     
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
 
}
     
  有些人認爲使用 volatile 的緣由是可見性,也就是能夠保證線程在本地不會存有 instance 的副本,每次都是去主內存中讀取。但實際上是不對的。使用 volatile 的主要緣由是其另外一個特性: 禁止指令重排序優化。也就是說,在 volatile 變量的賦值操做後面會有一個內存屏障(生成的彙編代碼上),讀操做不會被重排序到內存屏障以前。好比上面的例子,取操做必須在執行完 1-2-3 以後或者 1-3-2 以後,不存在執行到 1-3 而後取到值的狀況。從「先行發生原則」的角度理解的話,就是對於一個 volatile 變量的寫操做都先行發生於後面對這個變量的讀操做(這裏的「後面」是時間上的前後順序)。
    
     可是特別注意在  Java 5 之前的版本使用了 volatile 的雙檢鎖仍是有問題的。其緣由是 Java 5 之前的 JMM (Java 內存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能徹底避免重排序,主要是 volatile 變量先後的代碼仍然存在重排序問題。這個 volatile 屏蔽重排序的問題在 Java 5 中才得以修復,因此在這以後才能夠放心使用 volatile。
 
     因爲雙重檢查機制的複雜性,所以引入了下面的單例模式。
 
④餓漢式
 
public class Singleton{
    //類加載時就初始化
    private static final Singleton instance = new Singleton();   //使用final關鍵字保證instance對象只能初始化一次,其後不變
   
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}
   
    這種方式既保證了線程安全,有保證了性能,同時代碼也比較簡單,可是,仍然存在的問題是: 譬如 Singleton 實例的建立是依賴參數或者配置文件的,在 getInstance() 以前必須調用某個方法設置參數給它,那樣這種單例寫法就沒法使用了。
 
⑤靜態內部類形式
 
public class Singleton { 
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton(); 
    } 
    private Singleton (){} 
    public static final Singleton getInstance() { 
        return SingletonHolder.INSTANCE;
    } 
}
     
  這種寫法仍然使用JVM自己機制保證了線程安全問題;因爲 SingletonHolder 是私有的,除了 getInstance() 以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。
 
⑥枚舉類形式:
 
public enum EasySingleton{
    INSTANCE;   //默認是使用public static final修飾
}
 
     以上是使用枚舉類型完成的單例模式,簡單易用, 咱們能夠經過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了,建立枚舉默認就是線程安全的,下面介紹一下枚舉類型:
 
class Season{
    //1.提供類的屬性,聲明爲private final
    private final String seasonName;
    private final String seasonDesc;
    //2.聲明爲final的屬性,在構造器中初始化。
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    //3.經過公共的方法來調用屬性
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
    //4.建立枚舉類的對象:將類的對象聲明public static final
    public static final Season SPRING = new Season("spring", "春暖花開");    //若是隻有一個對象,那麼這個類就是一種單例模式的實現,和相似餓漢式,使用靜態本類屬性,一加載當前類就初始化,不一樣的是經過類名獲取單一實例
    public static final Season SUMMER = new Season("summer", "夏日炎炎");
    public static final Season AUTUMN = new Season("autumn", "秋高氣爽");
    public static final Season WINTER = new Season("winter", "白雪皚皚");
    @Override
    public String toString() {
        return "Season [seasonName=" + seasonName + ", seasonDesc="
                + seasonDesc + "]";
    }
    public void show(){
        System.out.println("這是一個季節");
    }
}

 

     經過以上代碼可使用Season.SPRING的形式獲取當前類對象,若是僅僅在內部定義的了一個SPRING對象,那麼這就是一種單例模式的實現。
 
總結:
     懶漢式、餓漢式、雙重檢查式都具有①一個靜態的獲取當前類對象的方法;②私有的構造函數;③一個私有的靜態自身類屬性
       內部類式:一樣具有上述上個條件,區別在於使用了一個靜態內部類,將外部類做爲內部類的私有靜態屬性初始化,外部類的靜態方法僅僅是獲取經過靜態內部類獲取外部類對象傳遞。
       枚舉類型:事實上沒有使用enum關鍵字以前,枚舉類型事實上是一種餓漢式的體現,與之不一樣的是枚舉類型的自身類屬性是public的,沒有經過一個靜態方法傳遞出去。
相關文章
相關標籤/搜索