synchronized內存可見性理解


一、背景

最近在看<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具有內存可見性,為了確保所有線程能夠看到共享變量的值是最新的,所有執行讀操作或寫操作的線程都必須在同一個鎖上同步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM