【java】關於java單例的思考(上)

菜雞一隻,如果有說錯的請大家批評指出,千萬別給我留面子,我也不想寫出誤導新人的文章來!!!

本文來整理整理相對基礎的東西:單例模式

 

該系列有兩篇文章:

【java】關於java單例的思考(上) :https://blog.csdn.net/lsr40/article/details/94195394

【java】關於java單例的思考(下):https://blog.csdn.net/lsr40/article/details/94316409

 

網上其實有許多文章在說單例模式,但是我覺得似乎說的不夠全面,所以我想寫寫我對於單例模式的理解。

 

1、什麼是單例

單例模式其實就是在爲了保證一個類(class)僅有一個實例

2、爲什麼要使用單例

節省多次new和回收對象所消耗的資源和時間

3、單例的實現方式

總的來說:

單例就是一個類的構造方法私有化(private)

並且提供一個靜態(static)的getInstance方法,讓外部調用,來獲得該類的實例化對象

4、單例的分類(網上的文章實在是有點亂,也可能是我水平不夠吧)

我暫時瞭解到的單例實現方式有四種(如果還有其他的希望大家能給我留言,我也會更新進來):

餓漢模式,懶漢模式(也叫飽漢模式),靜態內部類模式,單例註冊表模式

-1.餓漢模式

顧名思義,就是餓的等不及了,要先創建單例對象

public class IceCreamFactory {
    private static final IceCreamFactory iceCreamFactory = new IceCreamFactory();

    private IceCreamFactory(){}

    public static IceCreamFactory getInstance(){
        return iceCreamFactory;
    }
}

優點:不需要考慮什麼線程安全導致的性能問題,個人覺得這種模式還是比較方便的

缺點:在類被加載的時候,對象就會被實例化佔用內存,如果該對象一直未被用到,就比較浪費資源

-2.懶漢模式/飽漢模式

懶加載,飽了,不急着一開始就創建對象

public class IceCreamFactory {
    private static  IceCreamFactory iceCreamFactory ;

    private IceCreamFactory(){}

    public static IceCreamFactory getInstance() {
        if (iceCreamFactory == null) {             //代碼1
            iceCreamFactory = new IceCreamFactory();         //代碼2
        }
        return iceCreamFactory;
    }
}

好,那麼問題來了 

在多線程的情況下,這麼寫會出問題嗎?會的。

當線程A進入到“代碼1”的,iceCreamFactory對象null,然後他就要去執行“代碼2”創建對象,這時候剛好,資源被搶,線程B也進入到“代碼1”,iceCreamFactory對象還暫時爲null,然後線程B就也要去執行“代碼2”創建對象。這樣就會導致線程A和線程B拿到的對象不是同一個,破壞了單例原則!

然後就是一系列的優化(每一個優化都是爲了解決上一個優化的缺點或者問題),我簡單說下:

1、加同步方法關鍵字

2、加同步代碼塊

3、雙重爲空判斷

4、加上volatile關鍵詞

優化一:在getInstance方法上加上synchronized,就解決了線程安全的問題,但是會帶來性能的衰減,因爲當有一個線程進入到了方法內,其他所有線程都會被卡在方法外,代碼如下:

public class IceCreamFactory {
    private static  IceCreamFactory iceCreamFactory ;

    private IceCreamFactory(){}

    public synchronized static IceCreamFactory getInstance() {
        if (iceCreamFactory == null) {
            iceCreamFactory = new IceCreamFactory();
        }
        return iceCreamFactory;
    }
}

優化二:

方法不做同步,在方法內部需要同步的代碼上加上鎖,讓其他線程可以進到方法內,先執行一些不需要同步的前置代碼,加快不同線程的調用速度,但是這樣又會有一開始線程不安全的問題(不同線程都判斷iceCreamFactory對象null,然後都去new一個新的對象,結果返回不同的對象),代碼如下:

public class IceCreamFactory {
    private static  IceCreamFactory iceCreamFactory ;

    private IceCreamFactory(){}

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

優化三:

在同步代碼內,在做一次爲空判斷,這樣就算在外面判斷爲空的線程,進到同步代碼塊中還會判斷一次是否爲空,就可以避免優化二會遇到的問題

public class IceCreamFactory {
    private static  IceCreamFactory iceCreamFactory ;

    private IceCreamFactory(){}

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

優化四:

但是看似沒有問題的優化三,其實還是存在問題的,因爲new一個對象,這樣的操作不具有原子性,意思就是new雖然只有一行代碼,但是實際運行起來確有三個步驟:

1、申請一塊內存空間

2、在空間中實例化對象

3、將引用執行這塊空間地址(指向之後,就不爲null了)

Java內存模型並不限制處理器重排序,因此有可能會因爲存在三個步驟的原因出現問題,例如,先執行1,然後執行3,最後執行2。

在線程A還未在空間中實例化完對象,恰好另一個線程進入方法判斷該對象引用不爲null,然後就將其返回使用,導致出錯,所以優化四如下:

public class IceCreamFactory {
    private volatile static  IceCreamFactory iceCreamFactory ;

    private IceCreamFactory(){}

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

通過volatile關鍵詞來禁止jvm對該對象的重排優化,這樣就最終解決了懶漢模式的多線程和性能的問題。

如果想了解更多重排優化的情況,可以訪問:https://www.cnblogs.com/tuhooo/p/7921651.html(作者:tuhooo)

爲了不寫的太長,本文就到這裏。

 

後面還有兩種模式,大家請看下篇:

【java】關於java單例的思考(下):https://blog.csdn.net/lsr40/article/details/94316409

 

最近其實在看字節碼相關的東西,雖然看是看的差不多了,不過一直不知道從何寫起,寫了又刪,刪了又寫,因此暫時擱置,等我有更好的思路的時候再來編輯它。

還是老話,菜雞一隻(我也要努力學習充實自己啊)!!如果有什麼問題,或者寫錯的,歡迎大家留言討論與糾錯!!

 

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