Java對象延遲初始化的實現(xiàn)

一候学、什么是延遲初始化驯镊?

在Java多線程程序中锅锨,有時候需要采用延遲初始化來降低初始化類和創(chuàng)建對象的開銷敛苇。
延遲初始化實際上就是:當(dāng)我們要進行一些高開銷的對象初始化操作時妆绞,只有在使用這些對象時才進行初始化。最顯著的意義在于枫攀,假如程序?qū)嶋H上不會用到這些類括饶,那初始化它們的開銷就會被完全避免。

二来涨、延遲初始化的錯誤實現(xiàn)方式

1图焰、線程不安全的延遲初始化

public class UnsafeLazyInitialization {
    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) //1:A線程執(zhí)行
            instance = new Instance(); //2:B線程執(zhí)行
        return instance;
    }

    static class Instance {
    }
}

在UnsafeLazyInitialization類中,假設(shè)線程A執(zhí)行代碼1的同時蹦掐,B線程執(zhí)行代碼2技羔。此時線程A很可能看到instance引用的對象還沒有完成初始化。所以我們必須對1卧抗、2這兩步操作進行同步處理藤滥。

2、直接使用synchronized進行同步-有巨大的性能開銷

public class SafeLazyInitialization {
    private static Instance instance;

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

    static class Instance {
    }
}

在早期的JVM中社裆,使用synchronized存在巨大的性能開銷拙绊,這在實際的應(yīng)用中時幾乎不可能被接受的。

3泳秀、雙重檢查鎖定-看似聰明的解決方案

public class DoubleCheckedLocking { //1
    private static Instance instance; //2

    public static Instance getInstance() { //3
        if (instance == null) { //4:第一次檢查
            synchronized (DoubleCheckedLocking.class) { //5:加鎖
                if (instance == null) //6:第二次檢查
                    instance = new Instance(); //7:問題的根源出在這里
            } //8
        } //9
        return instance; //10
    } //11
    static class Instance {
    }
}

為了克服同步帶來的大量開銷标沪,人們想得到了雙重檢查鎖定這一看似聰明的解決方案。目的是僅僅對一開始競爭狀態(tài)的getInstance加鎖晶默,帶來開銷谨娜。

但由于代碼可能的重排序,直接使用上述代碼是一種錯誤的優(yōu)化磺陡。原因如下:
示例代碼第七行趴梢,即 instance = new Instance(); 可以分解為如下的三行偽代碼

memory = allocate();//1:分配對象的內(nèi)存空間
ctorInstance(memory);//2:初始化對象
instance = memory;//3:設(shè)置instance指向剛分配的內(nèi)存地址

假如2、3之間發(fā)生重排序币他,可能順序變成如下這樣

memory = allocate();//1:分配對象的內(nèi)存空間
instance = memory;//3:設(shè)置instance指向剛分配的內(nèi)存地址
ctorInstance(memory);//2:初始化對象

也就是說坞靶,當(dāng)instance已經(jīng)指向分配的內(nèi)存地址時,對象還沒有被初始化蝴悉。
我們再回到示例代碼

public class DoubleCheckedLocking { //1
    private static Instance instance; //2

    public static Instance getInstance() { //3
        if (instance == null) { //4:第一次檢查
            synchronized (DoubleCheckedLocking.class) { //5:加鎖
                if (instance == null) //6:第二次檢查
                    instance = new Instance(); //7:問題的根源出在這里
            } //8
        } //9
        return instance; //10
    } //11
    static class Instance {
    }
}

當(dāng)線程A在進行代碼的第7行彰阴,即new Instance時,內(nèi)部發(fā)生了重排序拍冠,即 instance = memory 在 ctorInstance(memory) 后進行尿这。假如進程A剛剛進行到這兩步之間簇抵。而進程B恰巧在第四行(第一次檢查)處進行判斷。那么線程B判斷instance不為null射众,很可能向下進行碟摆,進而訪問instance所引用的對象。而這時進程A尚未初始化instance叨橱!從而程序發(fā)生錯誤典蜕。

三、延遲初始化的正確實現(xiàn)方式

在上面的說明中了解了問題的根源后罗洗,我們可以很容易想到兩個方法來實現(xiàn)線程安全的延遲初始化愉舔。
(1)不允許偽代碼中的2和3兩行重排序
(2)允許2和3重排序,但不允許其他線程"看到"這個重排序

1伙菜、基于volatile的解決方案

我們只要對之前雙重檢查鎖定的代碼進行一些小小的修改轩缤,就可以實現(xiàn)我們期望中的延遲初始化。

public class SafeDoubleCheckedLocking {
    private volatile static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();//instance為volatile
            }
        }
        return instance;
    }

    static class Instance {
    }
}

當(dāng)instance的引用被聲明為volatile時贩绕,創(chuàng)建對象時的重排序就將在多線程環(huán)境中被禁止典奉。從而實現(xiàn)了用雙重檢查鎖定來實現(xiàn)延遲初始化。

注:這個方案需要JDK5以上版本丧叽,因為自JDK5開始使用新的JSR-133內(nèi)存模型,這個規(guī)范增強了volatile的語義公你。

2踊淳、基于類初始化鎖的解決方案

JVM在類的初始化階段(即Class被加載后,被線程使用前)陕靠,會執(zhí)行類的初始化迂尝。在執(zhí)行類的初始化期間,JVM會獲取一個鎖剪芥,這個鎖可以同步多個線程對一個類初始化垄开。基于這個特性税肪,我們可以用以下的方式來實現(xiàn)延遲初始化溉躲。
注意這個鎖是對于類的初始化,而非對象的益兄!

public class InstanceFactory {
    private static class InstanceHolder {//利用這個類的初始化鎖
        public static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance; //這里將InstanceHolder被初始化
    }
    static class Instance {
    }
}

當(dāng)getInstance第一次被調(diào)用锻梳,發(fā)生競爭時,InstanceHolder將被初始化净捅。其中的靜態(tài)變量instance也在此時被初始化疑枯。而InstanceHolder這個類的初始化鎖保證了instance的初始化是被同步的。即無論new instance時是否發(fā)生重排序蛔六,都不會被其他線程所看到荆永。從而解決了同步問題废亭。

類的初始化鎖相關(guān)知識在此不贅述,可參考《The Art of Java Concurrency Programming》相關(guān)篇目具钥。

四豆村、兩種解決方案的對比

我們很容易可以發(fā)現(xiàn),基于類初始化鎖的方案的實現(xiàn)代碼要更加簡潔氓拼。但基于volatile的雙重檢查鎖定方案有一個額外的優(yōu)勢你画,它可以對實例字段實現(xiàn)延遲初始化。
當(dāng)我們進行延遲初始化處理時桃漾,面對實例字段我們使用基于volatile的方案坏匪,面對靜態(tài)字段我們使用集于類初始化鎖的方案。

2021-1-16

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撬统,一起剝皮案震驚了整個濱河市适滓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恋追,老刑警劉巖凭迹,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異苦囱,居然都是意外死亡嗅绸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門撕彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鱼鸠,“玉大人,你說我怎么就攤上這事羹铅∈凑” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵职员,是天一觀的道長麻蹋。 經(jīng)常有香客問我,道長焊切,這世上最難降的妖魔是什么扮授? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮专肪,結(jié)果婚禮上糙箍,老公的妹妹穿的比我還像新娘。我一直安慰自己牵祟,他們只是感情好深夯,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般咕晋。 火紅的嫁衣襯著肌膚如雪雹拄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天掌呜,我揣著相機與錄音滓玖,去河邊找鬼。 笑死质蕉,一個胖子當(dāng)著我的面吹牛势篡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播模暗,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼禁悠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兑宇?” 一聲冷哼從身側(cè)響起碍侦,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隶糕,沒想到半個月后瓷产,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡枚驻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年濒旦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片再登。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡疤估,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霎冯,到底是詐尸還是另有隱情,我是刑警寧澤钞瀑,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布沈撞,位于F島的核電站,受9級特大地震影響雕什,放射性物質(zhì)發(fā)生泄漏缠俺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一贷岸、第九天 我趴在偏房一處隱蔽的房頂上張望腮鞍。 院中可真熱鬧潮售,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春崩掘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背少办。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工苞慢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人英妓。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓挽放,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蔓纠。 傳聞我的和親對象是個殘疾皇子辑畦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容