1. 面試官:雙重檢查鎖為什么要使用 volatile 字段?

        共 4903字,需瀏覽 10分鐘

         ·

        2021-06-24 15:15

        雙重鎖的由來(lái)

        單例模式中,有一個(gè)DCL(雙重鎖)的實(shí)現(xiàn)方式。在Java程序中,有時(shí)候可能需要推遲一些高開(kāi)銷的對(duì)象初始化操作,并且只有在使用這些對(duì)象時(shí)才開(kāi)始初始化。

        下面是非線程安全的延遲初始化對(duì)象的實(shí)例代碼。

        1. /**

        2. * @author

        3. */

        4. public class Instance {

        5. }


        6. /**

        7. * 非線程安全的延遲初始化對(duì)象

        8. *

        9. * @author

        10. */

        11. public class UnsafeLazyInitialization {

        12.    private static Instance instance;


        13.    public static Instance getInstance() {

        14.        if (null == instance) {

        15.            instance = new Instance();

        16.        }

        17.        return instance;

        18.    }

        19. }

        在UnsafeLazyInitialization類中,假設(shè)A線程執(zhí)行代碼1的同時(shí),B線程執(zhí)行代碼2。此時(shí),線程A可能會(huì)看到instance引用對(duì)象還沒(méi)有完成初始化。

        對(duì)于UnsafeLazyInitialization類,我們可以對(duì)getInstance()方法做同步處理來(lái)實(shí)現(xiàn)線程安全的延遲初始化。

        示例代碼如下。

        1. /**

        2. * 安全的延遲初始化

        3. *

        4. * @author

        5. */

        6. public class SafeLazyInitialization {

        7.    private static Instance instance;


        8.    public synchronized static Instance getInstance() {

        9.        if (null == instance) {

        10.            instance = new Instance();

        11.        }

        12.        return instance;

        13.    }

        14. }

        由于對(duì)getInstance()方法做了同步處理,synchronized將導(dǎo)致性能開(kāi)銷。如果getInstance()方法被多個(gè)線程頻繁的調(diào)用,將會(huì)導(dǎo)致程序執(zhí)行性能的下降。

        反之,如果getInstance()方法不會(huì)被多個(gè)線程頻繁的調(diào)用,那么這個(gè)延遲初始化方案將能提供令人滿意的性能。

        后來(lái),提出了一個(gè)“聰明”的技巧:雙重檢查鎖定(Double-Checked Locking)。想通過(guò)雙重檢查鎖定來(lái)降低同步的開(kāi)銷。

        下面是使用雙重檢查鎖定來(lái)實(shí)現(xiàn)延遲初始化的實(shí)例代碼。

        1. /**

        2. * 雙重檢查鎖定

        3. *

        4. * @author

        5. */

        6. public class DoubleCheckedLocking {

        7.    private static Instance instance;


        8.    public static Instance getInstance() {

        9.        if (null == instance) {                             //1.第一次檢查

        10.            synchronized (DoubleCheckedLocking.class) {     //2.加鎖

        11.                if (null == instance) {                     //3:第二次檢查

        12.                    instance = new Instance();              //4.問(wèn)題的根源出在這里

        13.                }

        14.            }

        15.        }

        16.        return instance;

        17.    }

        18. }

        雙重檢查鎖定看起來(lái)似乎很完美,但這是一個(gè)錯(cuò)誤的優(yōu)化!

        在線程執(zhí)行到第1處,代碼讀取到instance不為null時(shí),instance引用的對(duì)象有可能還沒(méi)有完成初始化。

        問(wèn)題的根源

        前面的雙重檢查鎖定實(shí)例代碼的第4處(instance = new Instance();)創(chuàng)建了一個(gè)對(duì)象。這一行代碼可以分解為如下的3行偽代碼。


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


        上面3行偽代碼中的2和3之間,可能會(huì)被重排序(在一些JIT編譯器上,這種重排序是真實(shí)發(fā)生的),2和3之間重排序之后的執(zhí)行時(shí)序如下:

        memory = allocate(); //1.分配對(duì)象的內(nèi)存空間
        instance = memory; //3.設(shè)置instance指向剛分配的內(nèi)存地址
                                                    //注意,此時(shí)對(duì)象還沒(méi)有被初始化!
        ctorInstance(memory); //2.初始化對(duì)象


        多線程執(zhí)行時(shí)序表

        • T1 A1:分配對(duì)象的內(nèi)存空間

        • T2 A3:設(shè)置instance指向內(nèi)存空間

        • T3 B1:判斷instance是否為空

        • T4 B2:由于instance不為null,線程B將訪問(wèn)instance引用的對(duì)象

        • T5 A2:初始化對(duì)象

        • T6 A4:訪問(wèn)instance引用的對(duì)象

        在知曉了問(wèn)題發(fā)生的根源之后,我們可以想出兩個(gè)方法來(lái)實(shí)現(xiàn)線程安全的延遲初始化。

        1)不允許2和3重排序2)允許2和3重排序,但不允許其他線程“看到”這個(gè)重排序。

        后文介紹的兩個(gè)解決方案,分別對(duì)應(yīng)于上面這兩點(diǎn)。

        解決方案一

        基于volatile的解決方案

        1. /**

        2. * 安全的雙重檢查鎖定

        3. *

        4. * @author

        5. */

        6. public class SafeDoubleCheckedLocking {

        7.    private volatile static Instance instance;


        8.    public static Instance getInstance() {

        9.        if (null == instance) {

        10.            synchronized (SafeDoubleCheckedLocking.class) {

        11.                if (null == instance) {

        12.                    instance = new Instance();//instance為volatile,現(xiàn)在沒(méi)有問(wèn)題了。

        13.                }

        14.            }

        15.        }

        16.        return instance;

        17.    }

        18. }

        注意:這個(gè)解決方案需要JDK5或更高版本(因?yàn)閺腏DK5開(kāi)始使用新的JSR-133內(nèi)存模型規(guī)范,這個(gè)規(guī)范增強(qiáng)了volatile的語(yǔ)義)。

        當(dāng)聲明對(duì)象的引用為volatile后,3行偽代碼中的2和3之間的重排序,在多線程環(huán)境中將會(huì)被禁止。

        解決方案二

        基于類初始化的解決方案

        JVM在類的初始化階段(即在Class被加載后,且被線程使用之前),會(huì)執(zhí)行類的初始化。

        在執(zhí)行類的初始化期間,JVM會(huì)去獲取一個(gè)鎖.這個(gè)鎖可以同步多個(gè)線程對(duì)同一個(gè)類的初始化。

        基于這個(gè)特性,可以實(shí)現(xiàn)另一種線程安全的延遲初始化方案(這個(gè)方案被稱之為Initialization On Demand Holder idiom)。

        1. public class InstanceFactory {

        2.    private static class InstanceHolder {

        3.        private static Instance instance = new Instance();

        4.    }


        5.    public static Instance getInstance() {

        6.        return InstanceHolder.instance; //這里將導(dǎo)致InstanceHolder類被初始化

        7.    }

        8. }

        字段延遲初始化降低了初始化類或創(chuàng)建實(shí)例的開(kāi)銷,但增加了訪問(wèn)被延遲初始化的字段的開(kāi)銷。

        在大多數(shù)時(shí)候,正常的初始化要優(yōu)于延遲初始化。如果確實(shí)需要對(duì)實(shí)例字段使用線程安全的延遲初始化,請(qǐng)使用上面介紹的基于volatile的延遲初始化的方案;如果確實(shí)需要對(duì)靜態(tài)字段使用線程安全的延遲初始化,請(qǐng)使用上面介紹的基于類初始化的方案。

        End

        作者:老男孩

        來(lái)源:

        https://blog.51cto.com/14230003/2454764

        瀏覽 59
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 91九色丨porny丨 | 日韩在线观看中文字幕 | 美女自慰18禁 | 日本边添边摸边做边爱的导演介绍 | 久久影院三级片 |