天天看點

volatile關鍵字的作用,synchronized

​​volatile關鍵字的作用​​

引言:以前隻是看過介紹volatile的文章,對其的了解也隻是停留在理論的層面上,由于最近在項目當中用到了關于并發方面的技術,是以下定決心深入研究一下java并發方面的知識。網上關于volatile的文章非常多,但是并沒有講解非常詳細的文章。(哪位要是有好的資料麻煩共享一份給我!)多數的都是一些理論講解,沒有實際的例子代碼,就算有代碼的也測試不出效果,總之理論總是與代碼不比對。

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

在Java線程并發進行中,有一個關鍵字volatile的使用目前存在很大的混淆,以為使用這個關鍵字,在進行多線程并發處理的時候就可以萬事大吉。

Java語言是支援多線程的,為了解決線程并發的問題,在語言内部引入了 同步塊(synchronized) 和 volatile 關鍵字機制。

synchronized(不做過多解釋)

同步塊大家都比較熟悉,通過 synchronized 關鍵字來實作,所有加上synchronized 和 塊語句,在多線程通路的時候,同一時刻隻能有一個線程能夠用

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

volatile

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

如果要深入了解volatile關鍵字的作用,就必須先來了解一下JVM在運作時候的記憶體配置設定過程。

在 java 垃圾回收整理一文中,描述了jvm運作時刻記憶體的配置設定。其中有一個記憶體區域是jvm虛拟機棧,每一個線程運作時都有一個線程棧,

線程棧儲存了線程運作時候變量值資訊。當線程通路某一個對象時候值的時候,首先通過對象的引用找到對應在堆記憶體的變量的值,然後把堆記憶體

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

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

描述這寫互動!

volatile關鍵字的作用,synchronized

那麼在了解完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

volatile關鍵字的作用,synchronized

問題出現了,為什麼我在主線程(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關鍵字,強制線程每次讀取該值的時候都去“主記憶體”中取值。在試試我們的程式吧,已經正常退出了。