標籤:資料拷貝 cep ace 分享 import 方法 space 進程 執行個體變數
直接先舉一個例子普通的線程執行個體變數的非可見度:
public class MyThread28 extends Thread{ private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } public void run() { System.out.println("進入run了"); while (isRunning == true){} System.out.println("線程被停止了"); }}
public static void main(String[] args){ try { MyThread28 mt = new MyThread28(); mt.start(); Thread.sleep(1000); mt.setRunning(false); System.out.println("已賦值為false"); } catch (InterruptedException e) { e.printStackTrace(); }}
看一下運行結果:
進入run了已賦值為false
也許這個結果有點奇怪,明明isRunning已經設定為false了, 線程還沒停止呢?
這就要從Java記憶體模型(JMM)說起,這裡先簡單講,虛擬機器那塊會詳細講的。根據JMM,Java中有一塊主記憶體,不同的線程有自己的工作記憶體,同一個變數值在主記憶體中有一份,如果線程用到了這個變數的話,自己的工作記憶體中有一份一模一樣的拷貝。每次進入線程從主記憶體中拿到變數值,每次執行完線程將變數從工作記憶體同步回主記憶體中。
注意這裡的情況只會出現Server模式,Client模式不會出現,而且要先睡一段時間再設定false,估計是為了讓子線程read load 好主記憶體欄位吧,不設定睡覺的話,子線程來不及 read load 就會立刻停止了
出現列印結果現象的原因就是主記憶體和工作記憶體中資料的不同步造成的。因為執行run()方法的時候拿到一個主記憶體isRunning的拷貝,而設定isRunning是在main函數中做的,換句話說 ,設定的isRunning設定的是主記憶體中的isRunning,更新了主記憶體的isRunning,線程工作記憶體中的isRunning沒有更新,當然一直死迴圈了,因為對於線程來說,它的isRunning依然是true。
解決這個問題很簡單,給isRunning關鍵字加上volatile。加上了volatile的意思是,每次讀取isRunning的值的時候,都先從主記憶體中把isRunning同步到線程的工作記憶體中,再當前時刻最新的isRunning。看一下給isRunning加了volatile關鍵字的運行效果:
進入run了已賦值為false線程被停止了
看到這下線程停止了,因為從主記憶體中讀取了最新的isRunning值,線程工作記憶體中的isRunning變成了false,自然while迴圈就結束了。
volatile之前的普通線程記憶體模型
使用 volatile 之後
這裡可以參考 java虛擬機器9:Java的記憶體模型
volatile的作用就是這樣,被volatile修飾的變數,保證了每次讀取到的都是最新的那個值。安全執行緒圍繞的是可見度和原子性這兩個特性展開的,volatile解決的是變數在多個線程之間的可見度,但是無法保證原子性。
多提一句,synchronized除了保障了原子性外,其實也保障了可見度。因為synchronized無論是同步的方法還是同步的代碼塊,都會先把主記憶體的資料拷貝到工作記憶體中(synchronized 只要一經線程調用,無所謂你鎖的誰,只要跟進程相關的執行個體變數,靜態變數都會同步到主記憶體,實現可見度,下面可以看到例子),同步代碼塊結束,會把工作記憶體中的資料更新到主記憶體中,這樣主記憶體中的資料一定是最新的。
volatile非原子特性
運行結果
證明了非原子性,但是保證是可見度的
下面更改代碼 使用 synchronized 代替 volatile
運行結果
使用 synchronized 就沒必要使用 volatile 了
synchronized擁有volatile的同步功能
可以看的出來 ·synchronized 只要一經線程調用,無所謂你鎖的誰,只要跟進程相關的執行個體變數,靜態變數都會同步到主記憶體,實現可見度
java 多線程11:volatile關鍵字