深入volatile關鍵字
- 2020 年 1 月 21 日
- 筆記
在Java多執行緒中,有一個特殊的關鍵字volatile,這個通常成為一個「輕量級鎖」,下面我們就來深入的了解這個關鍵的作用和原理。
執行緒的記憶體備份
首先看一段程式碼:
public class VolatileThread extends Thread { private boolean isRuning=true; private void setRuning(boolean runing){ this.isRuning=runing; } public void run(){ System.out.println("進入Run方法"); while (isRuning){ } System.out.println("執行緒結束"); } public static void main(String[] args) throws InterruptedException { VolatileThread volatileThread = new VolatileThread(); volatileThread.start(); Thread.sleep(3000); volatileThread.setRuning(false); System.out.println("runing設置成false,讓執行緒停止"); Thread.sleep(1000); System.out.println(volatileThread.isRuning); } }
在上面的程式碼並沒有列印出「執行緒結束」的資訊,因為我在主執行緒更改了isRuning 的值,並沒有影響到執行緒中的數據。
產生這個的原因是因為JDK在創建執行緒的時候,都會從主記憶體中拷貝一份數據,所以執行緒的讀取的變數的具有一定延遲

使用volatile
對上面的程式碼進行修改,把isRuning變數使用volatile 關鍵字修飾,這樣我們就能看到執行緒能夠正常的停止了。下面我們總結下volatile的作用
如果變數被volatile關鍵字修飾, 則當變數改變的時候強制執行緒從主記憶體中讀取和寫入變數
CPU的快取怎麼辦
程式碼最終的是由CPU執行的,為了保證CPU的執行效率,在讀取數據的時候,CPU是優先把數據快取到自己的高速快取中,高速快取帶來了效率上面的提高,也同樣帶來了數據一致性的問題。

例如下面這一段簡單的程式碼:
count++;
當程式運行的時候,count會被拷貝到CPU高速快取中,知道執行結束才會重新刷到主記憶體中。
如果在多執行緒的環境中,就會出現數據不一致的問題。
解決這個問題的方法有兩種:
- 在匯流排的位置加鎖,一次只允許一個CPU訪問記憶體。
- 使用快取一致性協議,當CPU發現當前的變數是volatile變數,就會被告知通知其他CPU告訴該變數的快取無效,這樣CPU就會從記憶體中重新載入數據
volatile 不具備原子性
共享變數只是在讀和寫的時候具有原子性,但是複雜的count++運算不具備原子性。
public class AppTest { private volatile int count = 0; public static void main(String[] args) throws InterruptedException { AppTest app = new AppTest(); for (int i = 0; i < 10000; i++) { Thread thread = new Thread(() -> { for (int m = 0; m < 1000; m++) { app.count(); } }); thread.start(); } System.out.println(app.count); for (int i=0;i<10;i++){ Thread.sleep(1000); System.out.println(app.count); } } private void count() { count++; } }
列印結果:
9444777 9578523 9578523
看到最終結果不是10000000。