<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 4903字,需瀏覽 10分鐘

           ·

          2021-06-24 15:15

          雙重鎖的由來

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

          下面是非線程安全的延遲初始化對象的實例代碼。

          1. /**

          2. * @author

          3. */

          4. public class Instance {

          5. }


          6. /**

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

          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的同時,B線程執(zhí)行代碼2。此時,線程A可能會看到instance引用對象還沒有完成初始化。

          對于UnsafeLazyInitialization類,我們可以對getInstance()方法做同步處理來實現(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. }

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

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

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

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

          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.問題的根源出在這里

          13.                }

          14.            }

          15.        }

          16.        return instance;

          17.    }

          18. }

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

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

          問題的根源

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


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


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

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


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

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

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

          • T3 B1:判斷instance是否為空

          • T4 B2:由于instance不為null,線程B將訪問instance引用的對象

          • T5 A2:初始化對象

          • T6 A4:訪問instance引用的對象

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

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

          后文介紹的兩個解決方案,分別對應(yīng)于上面這兩點。

          解決方案一

          基于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)在沒有問題了。

          13.                }

          14.            }

          15.        }

          16.        return instance;

          17.    }

          18. }

          注意:這個解決方案需要JDK5或更高版本(因為從JDK5開始使用新的JSR-133內(nèi)存模型規(guī)范,這個規(guī)范增強了volatile的語義)。

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

          解決方案二

          基于類初始化的解決方案

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

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

          基于這個特性,可以實現(xiàn)另一種線程安全的延遲初始化方案(這個方案被稱之為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ù)時候,正常的初始化要優(yōu)于延遲初始化。如果確實需要對實例字段使用線程安全的延遲初始化,請使用上面介紹的基于volatile的延遲初始化的方案;如果確實需要對靜態(tài)字段使用線程安全的延遲初始化,請使用上面介紹的基于類初始化的方案。

          End

          作者:老男孩

          來源:

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

          瀏覽 59
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日本中文在线 | 亚洲欧洲日本无码 | 国产地址| 亚洲无码三级片 | 国精产品一区一区三区四区 |