單例模式成型版

單例模式可以說是最容易理解的模式了,也是應用最廣的模式之一,先看看定義吧。


定義

確保單例類只有一個實例,並且這個單例類提供一個函數接口讓其他類獲取到這個唯一的實例。

什麼時候需要使用單例模式呢:如果某個類,創建時需要消耗很多資源,即new出這個類的代價很大;或者是這個類佔用很多內存,如果創建太多這個類實例會導致內存佔用太多。

關於單例模式,雖然很簡單,無需過多的解釋,但是這裏還要提個醒,其實單例模式裏面有很多坑。我們去會會單例模式。最簡單的單例模式如下:

public class TestSingle {

    private static TestSingle instance = null;

    private TestSingle(){}

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

}
如果是單線程下的系統,這麼寫肯定沒問題。可是如果是多線程環境呢?這代碼明顯不是線程安全的,存在隱患:某個線程拿到的instance可能是null,可能你會想,這有什麼難得,直接在getInstance()函數上加sychronized關鍵字不就好了。可是你想過沒有,每次調用getInstance()時都要執行同步,這帶來沒必要的性能上的消耗。注意,在方法上加sychronized關鍵字時,一個線程訪問這個方法時,其他線程無法同時訪問這個類其他sychronized方法。的我們看看另外一種實現:

public class TestSingle {

    private static TestSingle instance = null;

    private TestSingle() {
    }

    public static TestSingle getInstance() {
        if (instance == null) {
            synchronized (TestSingle.class) {
                if (instance == null) {
                    instance = new TestSingle();
                }
            }
        }
        return instance;
    }

}

爲什麼需要2次判斷是否爲空呢?第一次判斷是爲了避免不必要的同步,第二次判斷是確保在此之前沒有其他線程進入到sychronized塊創建了新實例。這段代碼看上去非常完美,但是,,,卻有隱患!問題出現在哪呢?主要是在instance=new Singleton();這段代碼上。這段代碼會編譯成多條指令,大致上做了3件事:

(1)給Singleton實例分配內存
(2)調用Singleton()構造函數,初始化成員字段
(3)將instance對象指向分配的內存(此時instance就不是null啦~)

上面的(2)和(3)的順序無法得到保證的,也就是說,JVM可能先初始化實例字段再把instance指向具體的內存實例,也可能先把instance指向內存實例再對實例進行初始化成員字段。考慮這種情況:一開始,第一個線程執行instance=new Singleton();這句時,JVM先指向一個堆地址,而此時,又來了一個線程2,它發現instance不是null,就直接拿去用了,但是堆裏面對單例對象的初始化並沒有完成,最終出現錯誤~ 。

看看另外一種方式:


public class TestSingle {

    private volatile static TestSingle instance = null;

    private TestSingle() {
    }

    public static TestSingle getInstance() {
        if (instance == null) {
            synchronized (TestSingle.class) {
                if (instance == null) {
                    instance = new TestSingle();
                }
            }
        }
        return instance;
    }

}

相比前面的代碼,這裏只是對instance變量加了一個volatile關鍵字volatile關鍵字的作用是:線程每次使用到被volatile關鍵字修飾的變量時,都會去堆裏拿最新的數據。換句話說,就是每次使用instance時,保證了instance是最新的。注意:volatile關鍵字並不能解決併發的問題,關於volatile請查看其它相關文章。但是volatile能解決我們這裏的問題。




































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