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

  • 2019 年 12 月 12 日
  • 筆記

來源:http://www.51testing.com

 首先貼一段測試代碼:

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後循環結束,線程終止。

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

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

  這裡結果輸出是:

Thread 2 finished.

  因為無法獲知線程2對共享變量running做出的修改, 然後線程1一直處在運行狀態。

  這裡簡單說明一下Java Mememory Model簡稱JMM:

  在本例中線程2實際上是修改了自己本地內存中的running值, 但是並沒有刷新到主內存中,線程1也一直在讀自己本地內存中的值,並沒有去主內存中重新獲取。

  為了讓例子最終能輸出

Thread 1 finished

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

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

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

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

  在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的兩條規定:

  線程解鎖前,必須把共享變量的最新值刷新到主內存中

  線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值

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

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

星雲測試

http://www.teststars.cc

奇林軟件

http://www.kylinpet.com

聯合通測

http://www.quicktesting.net