synchronized的可見性理解

  以前的時候看《併發編程的藝術》,書中提到dcl寫法的單例模式是有問題的,有可能會致使調用者獲得一個建立了一半的對象,從而致使報錯。修復辦法是將單例對象的引用添加volatile進行修飾,禁用重排序,則外界獲取的就必定是已經建立好的對象了。
  光說老是不行的,上代碼:
public class SingleTest {
    private static SingleTest singleTest;   // 這個應該用volatile修飾
    //獲取單例的方法
    public static SingleTest getInstance() {
        if(singleTest == null){
            synchronized (SingleTest.class){
                if(singleTest == null){
                    singleTest = new SingleTest();
                }
            }
        }
        return singleTest;
    }
}

  對於這一段的分析說的很清楚,網上也有大量的文章,但我有一個疑問:不是說synchronized有原子性、可見性麼,並且可見性是經過monitor exit的時候強制刷新內容到主內存來實現的,既然這樣,那synchornized結束前,沒有刷新到內存,外面的程序應該讀不到這個單例對象的值纔對啊,爲何會讀到呢?這個synchronized 的可見性究竟該怎麼理解?
  先說理解的錯誤之處:synchronized的可見性是經過monitor exit來保證的,這點沒錯,但monitor exit以前就不會刷新到主內存麼,顯然不是。如今jvm的機制,已經儘可能快速的將改變同步到緩存了,這個機制是怎麼肯定的不清楚,但簡單測試會發現很是短。
  另外,synchronized 的可見性的正確理解是:對於被synchronized修飾的代碼塊,若是A線程執行結束,會強制刷新線程緩存內容到內存,同時通知其它synchronized修飾的線程x的值無效,須要從新讀取(這點跟volatile很類似),所以B線程在執行的時候也就能讀到A線程對x的修改了,這就是synchronized的可見性。

  試一下以下示例:
//可見性驗證
@Test
public void testA() throws InterruptedException {
    //啓動線程在不停監視str變化
    Thread th1 = new Thread(() -> {
        while(true){
            if(str.equals("b")){
                System.out.println("th1 ==> str 已經被改成 b ," + Thread.currentThread());
            }
        }
    });
    Thread th2 = new Thread(() -> {
        while(true){
            synchronized (str){
                if(str.equals("b")){
                    System.out.println("th2 ==> str 已經被改成 b ," + Thread.currentThread());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
    th1.start();
    th2.start();

    //讓監視線程都啓動完畢
    Thread.sleep(3000);

    System.out.println("修改str的值爲b");
    synchronized (str){
        str = "b";
    }

    Thread.sleep(3000);
}
  執行結果:

  能夠看到th1並無輸出,由於它線程中的str換出內容一致是「a」。實際上,33-35行能夠不用synchronized,也會有相同結果,由於如今的jvm會盡最快速度將改變同步到緩存,而synchronized在執行的時候會從新讀取,所以也會發現str的值被改變了,而th1則沒有從新讀取的機制,也就沒法進行輸出了。
  對於monitor exit以前也會刷新到內存這點,也能夠經過程序進行驗證,能夠在synchronized中修改某個值,而後sleep一段時間,這期間讓另外一個線程去讀取被改變的值,會發現實際上是能夠讀到的。
相關文章
相關標籤/搜索