1、背景html
最近在看<Java併發編程實戰>這本書,看到共享變量的可見性,其中說到「加鎖的含義不單單侷限於互斥行爲,還包括內存可見性」。java
我對於內存可見性第一反應是volatile:被volatile修飾的變量可以保證每一個線程可以獲取該變量的最新值,從而避免出現數據髒讀的現象。編程
緣由是volatile修飾的共享變量進行寫操做的時候會多出Lock前綴的指令,經過多處理器的緩存一致性協議,來保持變量的同步更新。緩存
可是我卻沒明白「加鎖」與「可見性」這句話表達的意思,仔細思考下確實是這樣子的。併發
2、實踐ide
1 public class NoSivibilityVariable { 2 private static boolean isOver = false; 3 public static void main(String[] args) { 4 Thread thread = new Thread(new Runnable() { 5 @Override 6 public void run() { 7 while (!isOver); 8 System.out.println("thread ----- true"); 9 } 10 }); 11 thread.start(); 12 try { 13 Thread.sleep(500); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 isOver = true; 18 System.out.println("main ----- true"); 19 } 20 }
執行上面這段代碼,只會打印出「main ----- true」而後一直死循環阻塞。緣由是當主線程把變量isOver修改成true,值的修改做用範圍僅僅是當前線程內(主線程)而另外的線程是主內存的值,並無讀取主線程修改後的值,因此另外一個線程和主內存的值都是失效的值。spa
若是要解決這個狀況怎麼辦?線程
1.常見的作法就是把第二行 private static boolean isOver = false修改成 private static volatile boolean isOver = false就不會出現這種狀況。code
2.就像書中所說的經過加鎖來解決。htm
1 package synchronized_word; 2 3 public class NoSivibilityVariable { 4 private static boolean isOver = false; 5 6 public static void main(String[] args) { 7 Thread thread = new Thread(new Runnable() { 8 @Override 9 public void run() { 10 synchronized (NoSivibilityVariable.class) { 11 while (!isOver); 12 System.out.println("thread ----- true"); 13 } 14 } 15 }); 16 thread.start(); 17 18 19 try { 20 Thread.sleep(500); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 synchronized (NoSivibilityVariable.class) { 25 isOver = true; 26 } 27 System.out.println("main ----- true"); 28 } 29 }
2個線程中在對共享變量的讀取或者寫入都進行加鎖處理,由於線程對應的都是同一把鎖對象(該類對象)因此相互會排斥。可是就算這樣子也不能說明內存可見性的。其實真正解決這個問題的是JMM關於Synchronized的兩條規定:
一、線程解鎖前,必須把共享變量的最新值刷新到主內存中;
二、線程加鎖時,講清空工做內存中共享變量的值,從而使用共享變量是須要從主內存中從新讀取最新的值(加鎖與解鎖須要統一把鎖)
線程執行互斥鎖代碼的過程:
1.得到互斥鎖
2.清空工做內存
3.從主內存拷貝最新變量副本到工做內存
4.執行代碼塊
5.將更改後的共享變量的值刷新到主內存中
6.釋放互斥鎖
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#incorrectlySync
這裏提到synchronized會保證對進入同一個監視器的線程保證可見性。好比線程 t1修改了變量,退出監視器以前,會把修改變量值v1刷新的主內存當中;當線程t2進入這個監視器時,若是有某個處理器緩存了變量v1,首先緩存失效,而後必須重主內存從新加載變量值v1(這點和volatile很像)。這裏語義的解讀只是說了對於同一個監視器,變量的可見性有必定的方式可尋,非同一個監視器就不保證了。
3、總結
synchronized具備內存可見性,爲了確保全部線程可以看到共享變量的值是最新的,全部執行讀操做或寫操做的線程都必須在同一個鎖上同步。