設計模式——單例Singleton

若是你希望自己寫的程序中的某個類只能有一個相對應的實例,那麼這個時候就要用到單例模式了。單例模式是一種非常常見的設計模式,實現方法有好幾種,下面將一一介紹:

1.懶漢式

public class Singleton {
    private static Singleton instance;
    private Singleton(){}

    public static Singleton getInstance() {
        if(instance==null) {
            instance = new Singleton();
        }
        return instance;
    }
}

將構造方法設爲private的,這也令得我們無法在外部通過new來獲得Singleton類的實例,我們只能通過該類提供的getInstance()方法來獲得該類的一個實例。這種方法在單線程中是沒有問題的,而且實現簡單,但是在多線程環境下,設想一下多個線程同時訪問該代碼段,這將導致創建該類的多個實例,而這樣就違背了它作爲單例模式的初衷了,所以這種實現方法是線程不安全的。於是便有了下面這種改良版的懶漢式:

public class Singleton {
    private static Singleton instance;
    private Singleton(){}

    public static synchronized Singleton getInstance() {
        if(instance==null) {
            instance = new Singleton();
        }
        return instance;
    }
}

其實也就是在getInstance()方法前加了synchronized修飾,保證在同一時間只能有一個線程訪問該方法,這樣也就使得改良後的懶漢式是線程安全的。

2.雙重檢驗鎖
爲什麼會有雙重檢驗鎖呢,這是因爲改良後的懶漢式雖然採用了加鎖的方法解決了多實例的問題,但是其實我們只需要在第一次創建實例的時候進行同步操作而已,而懶漢式卻是使得整個getInstance方法在任何時候都只能被一個線程所調用,這樣做並不高效。於是便有了雙重檢驗鎖,也即double checked locking pattern,話不多說先上代碼:

public class Singleton {
    private static Singleton instance;
    private Singleton(){}

    public static synchronized Singleton getInstance() {
        if(instance==null) {   //first checked
            synchronized (Singleton.class) {
                if(instance==null) {   //second checked
                    instance = new Singleton();
                }
            }           
        }
        return instance;
    }
}

這裏只是對new一個實例的這一部分進行了同步操作,進行兩次判斷是爲了防止生成多個實例,因爲可能有多個線程同時進入同步代碼塊外的第一個if判斷語句。其實雙重檢驗鎖還有另外一種實現方式:

public class Singleton {
    private volatile static Singleton instance;
    private Singleton(){}

    public static synchronized Singleton getInstance() {
        if(instance==null) {   //first checked
            synchronized (Singleton.class) {
                if(instance==null) {   //second checked
                    instance = new Singleton();
                }
            }           
        }
        return instance;
    }
}

是的,兩種方式幾乎是一模一樣的,唯一不同的地方在於後者用volatile對instance進行了修飾。經過查閱資料瞭解到,”instance=new Singleton()”並不是一個原子操作,它將發生三個動作:(1)給instance分配內存(2)初始化Singleton的成員變量(3)將instance對象指向分配的內存空間。其中,(2)和(3)的順序不是固定的,這樣就有可能出現這樣的情況:線程A訪問該代碼段,1和3的動作相繼結束,2的動作還沒開始,然而這個時候線程B搶佔,由於3已經發生了,所以instance不等於null,線程B會返回尚未進行相關初始化的instance實例,這樣程序就error了。所以,引入volatile修飾符,它能夠禁止JVM對某代碼塊的指令重排序優化(沒錯,2和3發生的順序不固定就是由於JVM的指令重排序優化),從而有效防止上面error的發生。

3.餓漢式

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton(){}

    public static Singleton getInstance() {
        return instance;            
    }
}

之所以叫餓漢式,是因爲這種實現方法將使實例在類加載的時候便被創建(與懶漢式需要的時候才創建不同),從而也避免了多線程下的多實例問題。這樣子雖然簡單粗暴,但如果我們實在是需要在創建實例的時候傳遞些參數,那這種方法就不行了。

4.靜態內部類

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    private Singleton(){}
    public static final Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

這是一種被稱爲Initialization Demand Holder (IoDH)的技術,在內部類中創建instance實例使得這個動作不會在類加載中就發生,而是當我們第一次調用getInstance()方法時才發生,並且利用了JVM的特性保證了線程安全性,即該變量僅能被賦值一次。

5.枚舉
public enum Singleton{
INSTANCE;
}
這種實現方式簡單到了極點,而且枚舉類型本身就是線程安全的,看上去相當優美!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章