23種設計模式(一)單例模式

定義html

單例模式最初的定義出現於《設計模式》(艾迪生維斯理, 1994):「保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。」java

Java中單例模式定義:「一個類有且僅有一個實例,而且自行實例化向整個系統提供。」spring

主要解決:一個全局使用的類頻繁地建立與銷燬。設計模式

什麼時候使用:當您想控制實例數目,節省系統資源的時候。安全

特色框架

A這些類只能有一個實例;jvm

B可以自動實例化;函數

C這個類對整個系統可見,即必須向整個系統提供這個實例。優化

優勢.net

  1. 減小時間開銷
  2. 減小空間內存開銷

實例

  1. 系統經過spring框架注入的對象默認是單例的
  2. 配置文件在系統啓動時去遠端拉取,在後續使用時不須要再進行建立配置文件實例

實現方式

 單例模式的實現一般有兩種方式:「餓漢式」和「懶漢式」。

單例模式是將將構造方法限定爲private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的惟一實例只能經過提供的入口得到[例如getInstance()方法], 事實上,經過Java反射機制是可以實例化構造方法爲private的類的,那基本上會使全部的Java單例實現失效,咱們不考慮java反射機制

懶漢式

懶漢式在類建立的時候不會去建立實例,在第一次調用時纔會去建立,可是會有線程安全問題

餓漢式

 在類建立的時候建立出實例,這樣只會有一個實例被建立,沒有線程安全不安全的問題

http://5b0988e595225.cdn.sohucs.com/images/20171127/8476d360e3cf4d678d7f31010b6b5de5.jpeg

單例模式初版

public class Singleton {

    private Singleton() {} //私有構造函數      

    private static Singleton instance = null; //單例對象    

    // 靜態工廠方法      
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

爲何這樣寫呢?咱們來解釋幾個關鍵點:

1.要想讓一個類只能構建一個對象,天然不能讓它隨便去作new操做,所以Signleton的構造方法是私有的。

2.instance是Singleton類的靜態成員,也是咱們的單例對象。它的初始值能夠寫成Null,也能夠寫成new Singleton()。至於其中的區別後來會作解釋。

3.getInstance是獲取單例對象的方法。

若是單例初始值是null,還未構建,則構建單例對象並返回。這個寫法屬於單例模式當中的懶漢模式。

若是單例對象一開始就被new Singleton()主動構建,則再也不須要判空操做,這種寫法屬於餓漢模式

這兩個名字很形象:餓漢主動找食物吃,懶漢躺在地上等着人喂。

爲何說剛纔的代碼不是線程安全呢?

假設Singleton類剛剛被初始化,instance對象仍是空,這時候兩個線程同時訪問getInstance方法:

由於Instance是空,因此兩個線程同時經過了條件判斷,開始執行new操做:

這樣一來,顯然instance被構建了兩次。讓咱們對代碼作一下修改:

單例模式第二版:

public class Singleton {
    private Singleton() {} //私有構造函數         

    private static Singleton instance = null;//單例對象

    //靜態工廠方法          
    public static Singleton getInstance() {
        if (instance == null) { //雙重檢測機制
            synchronized (Singleton.class) {//同步鎖                                         
                if (instance == null) { //雙重檢測機制   
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

爲何這樣寫呢?咱們來解釋幾個關鍵點:

1.爲了防止new Singleton被執行屢次,所以在new操做以前加上Synchronized 同步鎖,鎖住整個類(注意,這裏不能使用對象鎖)。

2.進入Synchronized 臨界區之後,還要再作一次判空。由於當兩個線程同時訪問的時候,線程A構建完對象,線程B也已經經過了最初的判空驗證,不作第二次判空的話,線程B仍是會再次構建instance對象。

3.若是已經存在該對象的實例,再次進行訪問時不用再對對象進行加鎖操做。

像這樣兩次判空的機制叫作雙重檢測機制

假設這樣的場景,當兩個線程一先一後訪問getInstance方法的時候,當A線程正在構建對象,B線程剛剛進入方法:

這種狀況表面看似沒什麼問題,要麼Instance還沒被線程A構建,線程B執行 if(instance == null)的時候獲得false;要麼Instance已經被線程A構建完成,線程B執行 if(instance == null)的時候獲得true。

真的如此嗎?答案是否認的。這裏涉及到了JVM編譯器的指令重排。

指令重排是什麼意思呢?好比java中簡單的一句 instance = new Singleton,會被編譯器編譯成以下JVM指令:

memory =allocate(); //1:分配對象的內存空間

ctorInstance(memory); //2:初始化對象

instance =memory; //3:設置instance指向剛分配的內存地址

可是這些指令順序並不是一成不變,有可能會通過JVM和CPU的優化,指令重排成下面的順序:

memory =allocate(); //1:分配對象的內存空間

instance =memory; //3:設置instance指向剛分配的內存地址

ctorInstance(memory); //2:初始化對象

當線程A執行完1,3,時,instance對象還未完成初始化,但已經再也不指向null。此時若是線程B搶佔到CPU資源,執行 if(instance == null)的結果會是false,從而返回一個沒有初始化完成的instance對象。以下圖所示:

如何避免這一狀況呢?咱們須要在instance對象前面增長一個修飾符volatile。

單例模式第三版:

public class Singleton {
    private Singleton() {
    } //私有構造函數          

    private volatile static Singleton instance = null; //單例對象      


    //靜態工廠方法
    public static Singleton getInstance() {
        if (instance == null) { //雙重檢測機制        
            synchronized (Singleton.class) { //同步鎖   
                if (instance == null) { //雙重檢測機制     
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.

通過volatile的修飾,當線程A執行instance = new Singleton的時候,JVM執行順序是什麼樣?始終保證是下面的順序:

memory =allocate(); //1:分配對象的內存空間

ctorInstance(memory); //2:初始化對象

instance =memory; //3:設置instance指向剛分配的內存地址

如此在線程B看來,instance對象的引用要麼指向null,要麼指向一個初始化完畢的Instance,而不會出現某個中間態,保證了安全。

1. volatile關鍵字不但能夠防止指令重排,也能夠保證線程訪問的變量值是主內存中的最新值。有關volatile的詳細原理,自行百科。

 

用靜態內部類實現單例模式:

public class Singleton{
       private static class LazyHolder{
       private static final Singleton INSTANCE= newSingleton();
       }
       private Singleton(){}
       public static Singleton getInstance(){
              returnLazyHolder.INSTANCE;
       }
}

這裏有幾個須要注意的點:

1.從外部沒法訪問靜態內部類LazyHolder,只有當調用Singleton.getInstance方法的時候,才能獲得單例對象INSTANCE。

2.INSTANCE對象初始化的時機並非在單例類Singleton被加載的時候,而是在調用getInstance方法,使得靜態內部類LazyHolder被加載的時候。所以這種實現方式是利用classloader的加載機制來實現懶加載,並保證構建單例的線程安全

總結:

單例模式是爲了實現整個系統內部在運行過程當中只有一個實例,(餓漢 懶漢  線程安全  雙重判空  volidate防止jvm指令重排)

轉自: http://www.sohu.com/a/206960903_479559

單例模式其餘的實現方式,能夠參考:http://www.runoob.com/design-pattern/singleton-pattern.html

也能夠經過http://blog.csdn.net/jason0539/article/details/23297037/等博文,加深本身對單例模式的理解

相關文章
相關標籤/搜索