記一次對Java多線程內存可見性的測試

首先貼一段測試代碼:

public class TestMemoryBarrier {
    boolean running = false;

    boolean get() {
        return running;
    }

    void doSetTrue() {
        running = true;
    }

    public static void main(String[] args) throws InterruptedException {
        TestMemoryBarrier instance = new TestMemoryBarrier();

        new Thread(
            () -> {
                while (!instance.get()) {
                }

                System.out.println("Thread 1 finished.");
            }).start();

        Thread.sleep(100);

        new Thread(
            () -> {
                instance.doSetTrue();
                System.out.println("Thread 2 finished.");
            }).start();
    }
}

複製代碼

首先啓動第一個線程線程1去循環獲取bool變量running的值(默認是false),若是running變爲true後循環結束,線程終止。html

再起第二個線程線程2去修改running的值爲true,當修改完後輸出提示。bash

注意: 第一個線程啓動後主線程睡眠一段時間,確保第一個線程已經先於線程二獲取cpu時間片。

這裏結果輸出是:多線程

Thread 2 finished.
複製代碼

由於沒法獲知線程2對共享變量running作出的修改, 而後線程1一直處在運行狀態。app

這裏簡單說明一下Java Mememory Model簡稱JMM:測試

在本例中線程2其實是修改了本身本地內存中的running值, 可是並無刷新到主內存中,線程1也一直在讀本身本地內存中的值,並無去主內存中從新獲取。ui

爲了讓例子最終能輸出spa

Thread 1 finished
複製代碼

方法:很簡單, 直接設置變量running爲volatile,以保證其在多線程環境中的內存可見性問題。線程

本文的重點是對插入的內存屏障進行測試,因此以上只是開頭。3d

測試volatile插入的內存屏障指令,變動代碼爲:

添加一個類,包含一個volatile的變量並賦值code

get()方法return前, new一個這個新增類的實例對象,完整代碼:

class VolatileClass {
    private volatile int a = 1;
}
public class TestMemoryBarrier {
    boolean running = false;

    boolean get() {
        VolatileClass aClass = new VolatileClass();
        return running;
    }

    void doSetTrue() {
        running = true;
    }

    public static void main(String[] args) throws InterruptedException {
        TestMemoryBarrier instance = new TestMemoryBarrier();

        new Thread(
            () -> {
                while (!instance.get()) {
                }

                System.out.println("Thread 1 finished.");
            }).start();

        Thread.sleep(100);

        new Thread(
            () -> {
                instance.doSetTrue();
                System.out.println("Thread 2 finished.");
            }).start();
    }
}
複製代碼

這裏一樣能使線程1結束。緣由在於新建實例的時候對volatile變量進行了讀寫操做

volatile插入的內存屏障指令以下圖:(這裏不拉出重排序相關話題)

總結:在進行volatile變量寫的時候,StoreStore確保前面線程2修改的普通變量已經被flush到主內存中了。

測試synchronized關鍵字對可見性的影響:

爲了套用Happen-Before規則,這裏直接在get()doSetTrue()方法上加synchronized 也能保證可見性問題。但這裏假設只在get()方法上加同步呢? 結果是線程1也能獲取最新值。

JMM關於synchronized的兩條規定:

  1. 線程解鎖前,必須把共享變量的最新值刷新到主內存中
  2. 線程加鎖時,將清空工做內存中共享變量的值,從而使用共享變量時須要從主內存中從新讀取最新的值

若是隻是在doSetTrue()方法上加鎖,線程1並不會獲取最新的值,緣由是:雖然線程2已經將修改過的變量刷新到主存了,可是get()方法並不會去從主存讀取最新的值。

以上例子可能不符合happen-before規則,只是探討結果出現的緣由。

若是文章中出現對某處有錯誤理解的地方,歡迎你們指出。

參考文章:

相關文章
相關標籤/搜索