<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 關(guān)鍵字的作用

          共 3851字,需瀏覽 8分鐘

           ·

          2022-05-13 01:07

          作者 | 磊哥

          來源 | Java面試真題解析(ID:aimianshi666)

          轉(zhuǎn)載請聯(lián)系授權(quán)(微信ID:GG_Stone)

          volatile 是 Java 并發(fā)編程的重要組成部分,也是常見的面試題之一,它的主要作用有兩個:保證內(nèi)存的可見性和禁止指令重排序。下面我們具體來看這兩個功能。

          內(nèi)存可見性

          說到內(nèi)存可見性問題就不得不提 Java 內(nèi)存模型,Java 內(nèi)存模型(Java Memory Model)簡稱為 JMM,主要是用來屏蔽不同硬件和操作系統(tǒng)的內(nèi)存訪問差異的,因為在不同的硬件和不同的操作系統(tǒng)下,內(nèi)存的訪問是有一定的差異得,這種差異會導致相同的代碼在不同的硬件和不同的操作系統(tǒng)下有著不一樣的行為,而 Java 內(nèi)存模型就是解決這個差異,統(tǒng)一相同代碼在不同硬件和不同操作系統(tǒng)下的差異的。

          Java 內(nèi)存模型規(guī)定:所有的變量(實例變量和靜態(tài)變量)都必須存儲在主內(nèi)存中,每個線程也會有自己的工作內(nèi)存,線程的工作內(nèi)存保存了該線程用到的變量和主內(nèi)存的副本拷貝,線程對變量的操作都在工作內(nèi)存中進行。線程不能直接讀寫主內(nèi)存中的變量,如下圖所示:然而,Java 內(nèi)存模型會帶來一個新的問題,那就是內(nèi)存可見性問題,也就是當某個線程修改了主內(nèi)存中共享變量的值之后,其他線程不能感知到此值被修改了,它會一直使用自己工作內(nèi)存中的“舊值”,這樣程序的執(zhí)行結(jié)果就不符合我們的預期了,這就是內(nèi)存可見性問題,我們用以下代碼來演示一下這個問題:

          private?static?boolean?flag?=?false;
          public?static?void?main(String[]?args)?{
          ????Thread?t1?=?new?Thread(new?Runnable()?{
          ????????@Override
          ????????public?void?run()?{
          ????????????while?(!flag)?{

          ????????????}
          ????????????System.out.println("終止執(zhí)行");
          ????????}
          ????});
          ????t1.start();
          ????Thread?t2?=?new?Thread(new?Runnable()?{
          ????????@Override
          ????????public?void?run()?{
          ????????????try?{
          ????????????????Thread.sleep(1000);
          ????????????}?catch?(InterruptedException?e)?{
          ????????????????e.printStackTrace();
          ????????????}
          ????????????System.out.println("設置?flag=true");
          ????????????flag?=?true;
          ????????}
          ????});
          ????t2.start();
          }

          以上代碼我們預期的結(jié)果是,在線程 1 執(zhí)行了 1s 之后,線程 2 將 flag 變量修改為 true,之后線程 1 終止執(zhí)行,然而,因為線程 1 感知不到 flag 變量發(fā)生了修改,也就是內(nèi)存可見性問題,所以會導致線程 1 會永遠的執(zhí)行下去,最終我們看到的結(jié)果是這樣的:如何解決以上問題呢?只需要給變量 flag 加上 volatile 修飾即可,具體的實現(xiàn)代碼如下:

          private?volatile?static?boolean?flag?=?false;
          public?static?void?main(String[]?args)?{
          ????Thread?t1?=?new?Thread(new?Runnable()?{
          ????????@Override
          ????????public?void?run()?{
          ????????????while?(!flag)?{

          ????????????}
          ????????????System.out.println("終止執(zhí)行");
          ????????}
          ????});
          ????t1.start();
          ????Thread?t2?=?new?Thread(new?Runnable()?{
          ????????@Override
          ????????public?void?run()?{
          ????????????try?{
          ????????????????Thread.sleep(1000);
          ????????????}?catch?(InterruptedException?e)?{
          ????????????????e.printStackTrace();
          ????????????}
          ????????????System.out.println("設置?flag=true");
          ????????????flag?=?true;
          ????????}
          ????});
          ????t2.start();
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:

          禁止指令重排序

          指令重排序是指編譯器或 CPU 為了優(yōu)化程序的執(zhí)行性能,而對指令進行重新排序的一種手段。

          指令重排序的實現(xiàn)初衷是好的,但是在多線程執(zhí)行中,如果執(zhí)行了指令重排序可能會導致程序執(zhí)行出錯。指令重排序最典型的一個問題就發(fā)生在單例模式中,比如以下問題代碼:

          public?class?Singleton?{
          ????private?Singleton()?{}
          ????private?static?Singleton?instance?=?null;
          ????public?static?Singleton?getInstance()?{
          ????????if?(instance?==?null)?{?//?①
          ????????????synchronized?(Singleton.class)?{
          ????????????????if?(instance?==?null)?{
          ????????????????????instance?=?new?Singleton();?//?②
          ????????????????}
          ????????????}
          ????????}
          ????????return?instance;
          ????}
          }

          以上問題發(fā)生在代碼 ② 這一行“instance = new Singleton();”,這行代碼看似只是一個創(chuàng)建對象的過程,然而它的實際執(zhí)行卻分為以下 3 步:

          1. 創(chuàng)建內(nèi)存空間。
          2. 在內(nèi)存空間中初始化對象 Singleton。
          3. 將內(nèi)存地址賦值給 instance 對象(執(zhí)行了此步驟,instance 就不等于 null 了)。

          如果此變量不加 volatile,那么線程 1 在執(zhí)行到上述代碼的第 ② 處時就可能會執(zhí)行指令重排序,將原本是 1、2、3 的執(zhí)行順序,重排為 1、3、2。但是特殊情況下,線程 1 在執(zhí)行完第 3 步之后,如果來了線程 2 執(zhí)行到上述代碼的第 ① 處,判斷 instance 對象已經(jīng)不為 null,但此時線程 1 還未將對象實例化完,那么線程 2 將會得到一個被實例化“一半”的對象,從而導致程序執(zhí)行出錯,這就是為什么要給私有變量添加 volatile 的原因了。

          要使以上單例模式變?yōu)榫€程安全的程序,需要給 instance 變量添加 volatile 修飾,它的最終實現(xiàn)代碼如下:

          public?class?Singleton?{
          ????private?Singleton()?{}
          ????//?使用?volatile?禁止指令重排序
          ????private?static?volatile?Singleton?instance?=?null;?//?【主要是此行代碼發(fā)生了變化】
          ????public?static?Singleton?getInstance()?{
          ????????if?(instance?==?null)?{?//?①
          ????????????synchronized?(Singleton.class)?{
          ????????????????if?(instance?==?null)?{
          ????????????????????instance?=?new?Singleton();?//?②
          ????????????????}
          ????????????}
          ????????}
          ????????return?instance;
          ????}
          }

          總結(jié)

          volatile 是 Java 并發(fā)編程的重要組成部分,它的主要作用有兩個:保證內(nèi)存的可見性和禁止指令重排序。volatile 常使用在一寫多讀的場景中,比如 CopyOnWriteArrayList 集合,它在操作的時候會把全部數(shù)據(jù)復制出來對寫操作加鎖,修改完之后再使用 setArray 方法把此數(shù)組賦值為更新后的值,使用 volatile 可以使讀線程很快的告知到數(shù)組被修改,不會進行指令重排,操作完成后就可以對其他線程可見了。

          是非審之于己,毀譽聽之于人,得失安之于數(shù)。

          公眾號:Java面試真題解析

          面試合集:https://gitee.com/mydb/interview


          往期推薦

          每周匯總 | Java面試題(共43篇)2022版


          面試突擊43:lock、tryLock、lockInterruptibly有什么區(qū)別?


          面試突擊42:synchronized和ReentrantLock有什么區(qū)別?


          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  伊人午夜av | 国产精品成人7777777 | 成人A片一级片 | 国产综合乱伦三级 | 日韩国产精品一级毛片在线 |