標籤:記憶體模型 system 自己的 main 理解 工作 atomic mode 排序
一、JMM(java memory model)記憶體模型
從網上淘來二張圖:
上面這張圖說的是,在多核CPU的系統中,每個核CPU內建快取,然後電腦主板上也有一塊記憶體-稱為主內(即:記憶體條)。工作時,CPU的快取中的資料通過一系列手段來保證與主內的資料一致(CacheCoherence),更直白點,快取要從主內中load資料,處理完以後,還要save回主存。
說的是,java中的各種變數(variable)儲存在主存中,然後每個線程自己也有自己的工作記憶體區(working memory),工作時,線程從主存中把變數副本load到自己的工作記憶體區,處理完了,再save回主存。
好象很明白,沒有什麼不好理解的:),
問題來了,如果有二個線程:線程A與線程B, A從主存中讀取了變數x(到自己的的工作記憶體區),正準備處理,這時B修改了主存中的變數x,線程A能看見這種變化嗎?(是否需要及時從主存中,載入最新的值),這個問題稱為共用變數的可見度。
二、volatile、synchronized、AtomicXXX
直接上碼:
2.1 版本1
package test.cn.mwee.order.monitor;/** * Created by 菩提樹下的楊過 on 2017/6/11. */public class ThreadTest extends Thread { private static boolean flag = false; public void run() { System.out.println("t1:" + Thread.currentThread().getId()); while (!flag) { } System.out.println("quit!"); } public static void main(String[] args) throws InterruptedException { ThreadTest t1 = new ThreadTest(); t1.start(); Thread.sleep(50); ThreadTest.flag = true; System.out.println("main:" + Thread.currentThread().getId()); }}
ThreadTest是一個線程類,裡面有一個靜態變數flag,然後寫了個main方法做測試。
註:在t1啟動完成後,主線程中修改了ThreadTest的靜態變數值flag,這時t1的run方法裡的while迴圈,其實是看不見主線程對這個值的修改,所以程式始終不能退出,列印不出那一行quit.
2.2 版本2
package test.cn.mwee.order.monitor;/** * Created by 菩提樹下的楊過 on 2017/6/11. */public class ThreadTest extends Thread { private static boolean flag = false; public void run() { System.out.println("t1:" + Thread.currentThread().getId()); while (!flag) { synchronized (Class.class) { } } System.out.println("quit!"); } public static void main(String[] args) throws InterruptedException { ThreadTest t1 = new ThreadTest(); t1.start(); Thread.sleep(50); ThreadTest.flag = true; System.out.println("main:" + Thread.currentThread().getId()); }}
相對版本1,while迴圈中增加了一個synchronized同步代碼塊,雖然裡面啥代碼也沒有,但是再次運行,能正常quit了(想下為啥?)
答案:(也是從網上抄來的)
synchronized關鍵字強制實現一個互斥鎖,使得被保護的代碼塊在同一時間只能有一個線程進入並執行。同時也帶另外一個作用:線上程進入synchronized塊之前,會把工作存記憶體中的所有內容映射到主記憶體上,然後把工作記憶體清空再從主儲存空間上拷貝最新的值。而線上程退出synchronized塊時,同樣會把工作記憶體中的值對應到主記憶體,但此時並不會清空工作記憶體。這樣,工作記憶體中的值和主記憶體中的值是一致的,保證了資料的一致性!(換句話說,run中的while看到了主存中的flag變數值的改變)
思考題:如果在while{}裡寫一行println列印輸出,即:
while (!flag) { System.out.println("flag=" + flag); }
也能正常退出,留給大家去想。
2.3 版本3
package test.cn.mwee.order.monitor;/** * Created by 菩提樹下的楊過 on 2017/6/11. */public class ThreadTest extends Thread { private volatile static boolean flag = false; public void run() { System.out.println("t1:" + Thread.currentThread().getId()); while (!flag) { } System.out.println("quit!"); } public static void main(String[] args) throws InterruptedException { ThreadTest t1 = new ThreadTest(); t1.start(); Thread.sleep(50); ThreadTest.flag = true; System.out.println("main:" + Thread.currentThread().getId()); }}
相對版本1,flag變數前加了關鍵字volatile,它能保證對該變數的修改,同步到其它線程,即其它線程讀取flag時,看到的就是變化後的最新值,同時volatile還能防止指令重排序。運行下,也能如期列印出quit,程式退出。
註:volatile只能保證其它線程看到的變數值是最新的,但是並不保證原子性(換句話說,高並發情況下,仍然無法100%保證安全執行緒)
2.4 版本4
package test.cn.mwee.order.monitor;import java.util.concurrent.atomic.AtomicBoolean;/** * Created by 菩提樹下的楊過 on 2017/6/11. */public class ThreadTest extends Thread { private static AtomicBoolean flag = new AtomicBoolean(false); public void run() { System.out.println("t1:" + Thread.currentThread().getId()); while (!flag.get()) { } System.out.println("quit!"); } public static void main(String[] args) throws InterruptedException { ThreadTest t1 = new ThreadTest(); t1.start(); Thread.sleep(50); ThreadTest.flag.set(true); System.out.println("main:" + Thread.currentThread().getId()); }}
與上一個版本相比,使用了可以保證原子性,又不用加同步鎖的並發包裡的AtomicXXX系列類,同樣也可以正常列印出quit,推薦使用這個。
如果感興趣的話,可以看下AtomicBoolean的源碼,其實是藉助volatile以及CAS來實現的,源碼一看便知,不再囉嗦。
java學習:JMM(java memory model)、volatile、synchronized、AtomicXXX理解