一、背景
最近在看<Java並發編程實戰>這本書,看到共享變量的可見性,其中說到“加鎖的含義不僅僅局限於互斥行為,還包括內存可見性”。
我對於內存可見性第一反應是volatile:被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現數據臟讀的現象。
原因是volatile修飾的共享變量進行寫操作的時候會多出Lock前綴的指令,通過多處理器的緩存一致性協議,來保持變量的同步更新。
但是我卻沒明白“加鎖”與“可見性”這句話表達的意思,仔細思考下確實是這樣子的。
二、實踐
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,值的修改作用范圍僅僅是當前線程內(主線程)而另外的線程是主內存的值,並沒有讀取主線程修改后的值,所以另一個線程和主內存的值都是失效的值。
如果要解決這個情況怎么辦?
1.常見的做法就是把第二行 private static boolean isOver = false修改為 private static volatile boolean isOver = false就不會出現這種情況。
2.就像書中所說的通過加鎖來解決。
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、線程加鎖時,講清空工作內存中共享變量的值,從而使用共享變量是需要從主內存中重新讀取最新的值(加鎖與解鎖需要統一把鎖)
線程執行互斥鎖代碼的過程:
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很像)。這里語義的解讀只是說了對於同一個監視器,變量的可見性有一定的方式可尋,非同一個監視器就不保證了。
三、總結
synchronized具有內存可見性,為了確保所有線程能夠看到共享變量的值是最新的,所有執行讀操作或寫操作的線程都必須在同一個鎖上同步。