synchronized內存可見性理解

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具備內存可見性,爲了確保全部線程可以看到共享變量的值是最新的,全部執行讀操做或寫操做的線程都必須在同一個鎖上同步。

相關文章
相關標籤/搜索