volatile關鍵字的做用

引言:之前只是看過介紹volatile的文章,對其的理解也只是停留在理論的層面上,因爲最近在項目當中用到了關於併發方面的技術,因此下定決心深刻研究一下java併發方面的知識。網上關於volatile的文章很是多,可是並無講解很是詳細的文章。(哪位要是有好的資料麻煩共享一份給我!)多數的都是一些理論講解,沒有實際的例子代碼,就算有代碼的也測試不出效果,總之理論老是與代碼不匹配。

後來在我不懈的努力之下總算研究出一些成果,在此分享給你們!若是你們發現有錯誤的地方歡迎你們指正,謝謝!

 

在Java線程併發處理中,有一個關鍵字volatile的使用目前存在很大的混淆,覺得使用這個關鍵字,在進行多線程併發處理的時候就能夠萬事大吉。html

Java語言是支持多線程的,爲了解決線程併發的問題,在語言內部引入了 同步塊(synchronized) 和 volatile 關鍵字機制。java

 

synchronized(不作過多解釋)多線程

同步塊你們都比較熟悉,經過 synchronized 關鍵字來實現,全部加上synchronized 和 塊語句,在多線程訪問的時候,同一時刻只能有一個線程可以用併發

synchronized 修飾的方法 或者 代碼塊。jvm

 

volatile性能

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

 

若是要深刻了解volatile關鍵字的做用,就必須先來了解一下JVM在運行時候的內存分配過程。優化

 

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

 

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

 

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

 

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

 

描述這寫交互!

 

 

 

那麼在瞭解完JVM在運行時候的內存分配過程之後,咱們開始真正深刻的討論volatile的具體做用

請看代碼:

 

複製代碼
 1 public class VolatileTest extends Thread {
 2     
 3     boolean flag = false;
 4     int i = 0;
 5     
 6     public void run() {
 7         while (!flag) {
 8             i++;
 9         }
10     }
11     
12     public static void main(String[] args) throws Exception {
13         VolatileTest vt = new VolatileTest();
14         vt.start();
15         Thread.sleep(2000);
16         vt.flag = true;
17         System.out.println("stope" + vt.i);
18     }
19 }
複製代碼

 

上面的代碼是經過標記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循環 

 

 7         while (!flag) {
 8             i++;
 9         }

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關鍵字,強制線程每次讀取該值的時候都去「主內存」中取值。在試試咱們的程序吧,已經正常退出了。

 

https://www.cnblogs.com/daxin/p/3364014.html

相關文章
相關標籤/搜索