Java併發之volatile關鍵字內存可見性問題

Java併發之volatile關鍵字內存可見性問題

線程之間數據共享案例

我們先來看一個場景:

Main函數啟動後,調用一個線程向list中添加數據。List的size為5的時候,設置變量flag為true.然後,主線程根據flag的值進行其他操作。

代碼如下:

Java併發之volatile關鍵字內存可見性問題

運行結果:

Java併發之volatile關鍵字內存可見性問題

我們發現,當子線程輸出flag為ture後,主線程也沒有輸出=====。

這是為什麼呢?

線程在內存中運行簡圖

我們來看看上面程序在內存中怎麼運行的

Java併發之volatile關鍵字內存可見性問題

運行說明:

當程序運行的時候,先從main函數,主線程開始的,main線程先將flag=false 複製到自己程序的內存中;

這個時候開啟了子線程,子線程同樣將flag=false複製到自己程序內存中,在執行自己內部代碼後,修改了flag的值,回寫到主內存中(相對於程序自己的內存來說,內存中的數據是主內存。程序自己的內存其實就是複製了一份主內存的數據)。

如下圖:

Java併發之volatile關鍵字內存可見性問題

因為main線程沒有刷新,沒有從主線程獲取最新的flag的值。所以,控制檯上始終不能輸出===。

結果分析

那麼為什麼會出現這種情況呢?【這裡就需要知道兩個概念:編譯器和寄存器】

那是因為編譯器會自動優化的結果。

編譯器優化:在線程內,當讀取到一個變量的時候,為了提高讀寫(存取)的速度,編譯器在優化的時候,會先把變量讀取到一個寄存器(對應上圖子線程自己的內存或者是main線程自己的內存)中;以後在取這個變量的時候,就直接從寄存器中獲取了;

當變量的值在本線程裡面改變的時候,會同時把變量的新值同步到該寄存器中,以便保持一致;同時JVM就會向處理器發送一條指令,將這個變量所在的寄存器的值回寫到系統內存(對應上圖中的主內存)中。

造成數據不一致的原因:

當變量再因為別的其他線程操作而改變了值,該寄存器的值不會相應的改變,從而造成應用程序讀取的值和實際的變量值不一致(如上圖案例中,子線程修改了flag的值,但不會修改main線程寄存器裡面的值。這個是站在變量角度來說的);

或者當該寄存器再因為別的其他線程改變了變量的值,原來變量的值不會改變,從而造成了應用程序讀取的值和實際的變量值不一致(這個是從寄存器角度來說的。如上圖案例中,main線程的寄存器裡面是false,但是子線程已經修改成了ture).

從上面案例中,我們發現無論是主線程main函數還是子線程thread都是對變量flag進行操作的。這個時候,我們就說變量flag是線程之間共享數據了。而主內存(也就是系統內存非程序自己需要的內存)flag變量對所有共享這個變量的線程來說,都應該是可見的才可以。

那麼這個時候,在Java中怎麼實現線程之間共享數據的內存可見性呢?這裡就是我們今天需要講解的關鍵字:volatile。【ps:還有其他方案可以解決,如同步鎖】

Volatile關鍵字

Volatile中文意思:易變的;不穩定的

Volatile關鍵字是一種類型修飾符,用它來聲明的變量表示不可以別編譯器未知因素更改。當編譯器在編譯過程中,遇到這個關鍵字聲明的變量的時候,便一切都會對訪問該變量的代碼不再進行優化,從而可以提供特殊的地址來保證穩定訪問。

通俗理解:當JVM遇到該關鍵字修飾的變量的時候,就會不允許編譯器和處理器對指令序列進行重排(默認為了優化性能,JVM允許編譯器和處理器對指令序列進行重排的)。

JVM對編譯器指定的volatile規則表:

Java併發之volatile關鍵字內存可見性問題

我們將flag用volatile修飾後,在運行程序,查看運行後結果:

Java併發之volatile關鍵字內存可見性問題

我們可以看到,主線程輸入了===,子線程也輸出了當前標記為true。說明volatile起作用了。

Volatile和Synchronized 關鍵字的區別

1:Volatile是輕量級的同步策略;Synchronized是重量級的;

2:volatile不具備互斥性的,Synchronize是互斥的;

3:volatile不能夠保證變量的原子性。

Volatile的使用場景

必備條件

在使用volatile的時候需要滿足以下兩個條件:

1:對變量的寫操作不能依賴於當前的值。

比如上文中flag的值修改成true的時候,不受當前flag值的影響

2:該變量不能被包含在具有其他變量的不變式中。

比如int i ;y=i+1;這種情況不允許的。這個變量不能被其他變量作為不變式用。

只有在狀態真正的獨立於程序內其他內容的時候才可以使用volatile.

適用於場景一:狀態標誌

場景二:開銷較低的讀-寫鎖策略

場景三:單例中的雙重校驗

總結

Volatile可以解決多線程操作共享數據時候解決內存可見性問題。

簡單理解:被volatile修飾的變量,編譯器不會去優化調用該變量的程序(也就是不會把變量放到調用程序的寄存器中),程序調用的時候都是實時從主內存中獲取到最新的數據。

Java併發之volatile關鍵字內存可見性問題


分享到:


相關文章: