Java設計模式之單例模式(Singleton Pattern)

_小馬快跑_發表於2017-12-15

**單例模式:用來創造獨一無二的,只能有一個例項的物件設計模式。單例模式確保一個類只有一個例項,並提供一個全域性訪問點。**相比於全域性變數(對物件的靜態引用),單例模式可以延遲例項化,而且全域性變數不能保證只有一個例項。

UML圖:

singleton.png

**如何保證只有一個例項呢?**我們可以構造器的修飾符變為private,這樣外部類就不能通過new來獲取例項了,還記得修飾符對應的作用域嗎?看下圖:

modifier.png

確定了構造器為private之後,接著再定義一個static方法供全域性訪問來獲得這個單例,首先可以先這樣實現:

 private static SingleTon uniqueInstance;
 //私有構造器
 private SingleTon() {
 }
 //定義static供全域性訪問
 public static SingleTon getInstance() {
     if (uniqueInstance == null) {
        uniqueInstance = new SingleTon();
     }
     return uniqueInstance;
 }
複製程式碼

當你正在得意單例模式愉快地搞定了的時候,殊不知程式碼有個致命的隱患:多執行緒。假如專案中不會用到多執行緒,那麼上面的程式碼已經夠用了。但如果專案中會用到多執行緒,比如專案中有2個執行緒,執行緒A執行到getInstance()方法中的if (uniqueInstance == null)了,這時CPU去執行執行緒B,而執行緒B恰巧也執行到getInstance()方法中的if (uniqueInstance == null)了,此時uniqueInstance 還沒有被初始化,所以執行緒A和執行緒B都會去初始化類,導致單例失效,存在多個例項,解決方法就是加同步鎖:synchronized。下面給出幾種不同的實現方式:

1、如果對效能要求不高,可以直接簡單粗暴地加到getInstance()前面:

    private static SingleTon uniqueInstance;
    private SingleTon() {
    }
    public static synchronized SingleTon getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new SingleTon();
        }
        return uniqueInstance;
    }
複製程式碼

如果將getInstance()執行在頻繁執行的地方,上面的單例會讓執行效率大大下降,接著看下面的方式。

2、餓漢式單例:

 //在靜態初始化器中建立例項
 private static SingleTon uniqueInstance = new SingleTon();
 private SingleTon() {
 }
 public static SingleTon getInstance() {
     //已經有了例項,直接使用
     return uniqueInstance;
 }
複製程式碼

在靜態初始化器中建立單例,這樣就保證了執行緒安全,在JVM載入這個類時馬上建立了唯一的例項,這樣就保證了任何執行緒訪問uniqueInstance靜態變數之前,一定先建立了此例項。餓漢式單例優點是不用使用同步鎖,保證了執行緒安全;缺點也很明顯,類載入時就初始化了例項,假如getInstance()沒有使用,浪費了記憶體。

3、懶漢式單例(雙重加鎖式):

 private volatile static SingleTon uniqueInstance;
 private SingleTon() {
 }
 public static SingleTon getInstance() {
     //檢查例項,如果不存在就進入同步區
     if (uniqueInstance == null) {
          //只有第一次才會執行同步鎖塊
         synchronized (SingleTon.class) {
             //進入同步塊內,例項仍是null的時候才去建立例項
             if (uniqueInstance == null) {
                 uniqueInstance = new SingleTon();
             }
         }
     }
     return uniqueInstance;
 }
複製程式碼

**volatile關鍵字:**保證資料的可見性,即當一個共享變數被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他執行緒需要讀取時,它會去記憶體中讀取最新的值。也就是說兩個執行緒同時操作一個變數,那麼一個執行緒A對這個變數的寫操作一定先行發生於另一個執行緒B對這個變數的讀操作。所以當uniqueInstance變數被初始化成SingleTon例項時,volatile能保證多個執行緒正確的處理uniqueInstance變數。 雙重加鎖式保證了只有第一次呼叫時才會執行同步鎖塊,後面再呼叫就不會執行同步鎖塊了,相比於1中的方式,效能大大提升。

(2017.08.06更新) 4、靜態內部類:

public class InnerSingleTon {

    private InnerSingleTon() {
    }

    public static InnerSingleTon getInstance() {
        return InnerSingle.instance;
    }

    private static class InnerSingle {
        private static InnerSingleTon instance = new InnerSingleTon();
    }

}
複製程式碼

由於靜態內部類只載入一次,所以這種方式是執行緒安全的!

針對不同情況,可以選擇上面的一種來實現單例模式!

相關文章