單例模式為什么要用Volatile關(guān)鍵字

線程安全的單例模式常見寫法是雙重檢查加鎖。代碼如下:


class Singleton{

    private volatile static Singleton singleton;



    private Singleton(){}       

    public static Singleton getInstance(){       

        if(singleton == null){                  // 1

            synchronized(Singleton.class){      // 2

                if(singleton == null){          // 3

                    singleton = new Singleton(); // 4

                }

            }

        } 

        return singleton;           

    }

}

雙重檢查加鎖的單例模式代碼上就比較復雜,尤其體現(xiàn)在getInstance方法上,包括兩次檢查singleton是否是null,一次加鎖,singleton用關(guān)鍵字volatile修飾。為什么寫一個單例如此復雜呢?

首先是懶漢模式,實例的初始化延遲到getInstance方法中,為了保證只會生成一個實例,要先判斷singleton是否已經(jīng)初始化,如果已經(jīng)初始化了,就返回singleton,沒有的話就創(chuàng)建對象。這就是1的作用。如果是單線程的情況,這樣就夠用了。

在多線程的情況下,只有1,沒有2,3,就可能導致創(chuàng)建多個實例。例如,線程A和線程B調(diào)用getInstance方法,線程A先判斷了1,然后時間片結(jié)束了,切換到線程B,線程B判斷1,然后創(chuàng)建了singleton。時間片有切會線程A,線程A創(chuàng)建實例。這樣就線程A和線程B就分別創(chuàng)建了一個實例了。破壞了單例的結(jié)構(gòu)。

為了解決這個問題,加了synchronized保證只有一個線程進入臨界區(qū)。那只有2,沒有3,可以嗎?還是考慮和前面一模一樣的場景,這次線程A和線程B都判斷了1了,進入2,線程A先進入臨界區(qū),線程B發(fā)現(xiàn)線程A進入了臨界區(qū),就掛在了Singleton.class等等待隊列中,等待線程A執(zhí)行完成。線程A繼續(xù)執(zhí)行,創(chuàng)建了一個singleton實例。退出了臨界區(qū)。然后線程B被喚醒,進入臨界區(qū),又創(chuàng)建了一個singleton實例。結(jié)果又創(chuàng)建了兩個singleton實例。

所以3的作用很明顯了。在上面例子中,如果線程B發(fā)現(xiàn)實例已經(jīng)被創(chuàng)建了(singleton不等于null),就直接退出臨界區(qū)了。那1和3的作用似乎有點重合了,1似乎就不是必須了。2,3確實就足夠保證單例了。但是加鎖是比較消耗資源的,1就是為了減少資源的消耗。

最后,這么看來1,2,3,4就足以保證單例了。那為什么需要加volatile呢?volatile就牽扯到指令重排序的問題了。

要理解為什么要加volatile,首先要理解new Singleton()做了什么。new一個對象有幾個步驟。1.看class對象是否加載,如果沒有就先加載class對象,2.分配內(nèi)存空間,初始化實例,3.調(diào)用構(gòu)造函數(shù),4.返回地址給引用。而cpu為了優(yōu)化程序,可能會進行指令重排序,打亂這3,4這幾個步驟,導致實例內(nèi)存還沒分配,就被使用了。

再用線程A和線程B舉例。線程A執(zhí)行到new Singleton(),開始初始化實例對象,由于存在指令重排序,這次new操作,先把引用賦值了,還沒有執(zhí)行構(gòu)造函數(shù)。這時時間片結(jié)束了,切換到線程B執(zhí)行,線程B調(diào)用new Singleton()方法,發(fā)現(xiàn)引用不等于null,就直接返回引用地址了,然后線程B執(zhí)行了一些操作,就可能導致線程B使用了還沒有被初始化的變量。

加了volatile之后,就保證new 不會被指令重排序。

至此,這就是一個完整的懶漢模式—>線程安全的->雙重檢查加鎖單例模式。

推薦另一種單例模式的寫法:

class Singleton{
    private Singleton(){}
    
    private static class LazySomethineHolder{
        public static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance(){
        return LazySomethineHolder.singleton;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容