volatile關鍵字的作用


轉載https://www.cnblogs.com/xd502djj/p/9873067.html

volatile關鍵字

volatile簡述

用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改后的最的值。volatile很容易被誤用,用來進行原子性操作。

如果要深入了解volatile關鍵字的作用,就必須先來了解一下JVM在運行時候的內存分配過程。

在 java 垃圾回收整理一文中,描述了jvm運行時刻內存的分配。其中有一個內存區域是jvm虛擬機棧,每一個線程運行時都有一個線程棧,

線程棧保存了線程運行時候變量值信息。當線程訪問某一個對象時候值的時候,首先通過對象的引用找到對應在堆內存的變量的值,然后把堆內存

變量的具體值load到線程本地內存中,建立一個變量副本,之后線程就不再和對象在堆內存變量值有任何關系,而是直接修改副本變量的值,

在修改完之后的某一個時刻(線程退出之前),自動把線程變量副本的值回寫到對象在堆中變量。這樣在堆中的對象的值就產生變化了。如下圖所示:

實戰應用

那么在了解完JVM在運行時候的內存分配過程以后,我們開始真正深入的討論volatile的具體作用

public class VolatileTest extends Thread {

    boolean flag = false;
    int i = 0;

    public void run() {
        while (!flag) {
            i++;
        }
    }

    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(2000);
        vt.flag = true;
        System.out.println("stope" + vt.i);
    }
}

上面的代碼是通過標記flag來控制VolatileTest線程while循環退出的例子!

下面讓我用偽代碼來描述一下我們的程序

  • 首先創建 VolatileTest vt = new VolatileTest();
  • 然后啟動線程 vt.start();
  • 暫停主線程2秒(Main) Thread.sleep(2000);
  • 這時的vt線程已經開始執行,進行i++;
  • 主線程暫停2秒結束以后將 vt.flag = true;
  • 打印語句 System.out.println("stope" + vt.i); 在此同時由於vt.flag被設置為true,所以vt線程在進行下一次while判斷 while (!flag) 返回假 結束循環 vt線程方法結束退出!
  • 主線程結束

上面的敘述看似並沒有什么問題,“似乎”完全正確。那就讓我們把程序運行起來看看效果吧,執行mian方法。2秒鍾以后控制台打印stope-202753974。

可是奇怪的事情發生了 程序並沒有退出。vt線程仍然在運行,也就是說我們在主線程設置的 vt.flag = true;沒有起作用。

在這里我需要說明一下,有的同學可能在測試上面代碼的時候程序可以正常退出。那是因為你的JVM沒有優化造成的!在DOC下面輸入 java -version 查看 如果顯示Java HotSpot(TM) ... Server 則JVM會進行優化。

如果顯示Java HotSpot(TM) ... Client 為客戶端模式,需要設置成Server模式 設置方法問Google

疑問

問題出現了,為什么我在主線程(main)中設置了vt.flag = true; 而vt線程在進行判斷flag的時候拿到的仍然是false?

那么按照我們上面所講的 “JVM在運行時候的內存分配過程” 就很好解釋上面的問題了。

首先 vt線程在運行的時候會把 變量 flag 與 i (代碼3,4行)從“主內存” 拷貝到 線程棧內存(上圖的線程工作內存)

然后 vt線程開始執行while循環

while (!flag) {
    i++;
}

while (!flag)進行判斷的flag 是在線程工作內存當中獲取,而不是從 “主內存”中獲取。

i++; 將線程內存中的i++; 加完以后將結果寫回至 "主內存",如此重復。

然后再說說主線程的執行過程。 我只說明關鍵的地方

vt.flag = true;

主線程將vt.flag的值同樣 從主內存中拷貝到自己的線程工作內存 然后修改flag=true. 然后再將新值回到主內存。

這就解釋了為什么在主線程(main)中設置了vt.flag = true; 而vt線程在進行判斷flag的時候拿到的仍然是false。那就是因為vt線程每次判斷flag標記的時候是從它自己的“工作內存中”取值,而並非從主內存中取值!

這也是JVM為了提供性能而做的優化。那我們如何能讓vt線程每次判斷flag的時候都強制它去主內存中取值呢。這就是volatile關鍵字的作用。

再次修改我們的代碼

public class VolatileTest extends Thread {
    
    volatile boolean flag = false;
    int i = 0;
    
    public void run() {
        while (!flag) {
            i++;
        }
    }
    
    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(2000);
        vt.flag = true;
        System.out.println("stope" + vt.i);
    }
}

在flag前面加上volatile關鍵字,強制線程每次讀取該值的時候都去“主內存”中取值。在試試我們的程序吧,已經正常退出了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM