在Java記憶體模型中測試並發程式碼_java

來源:互聯網
上載者:User

讓我們來看看這段代碼:
 

import java.util.BitSet;import java.util.concurrent.CountDownLatch; public class AnExample {   public static void main(String[] args) throws Exception {    BitSet bs = new BitSet();    CountDownLatch latch = new CountDownLatch(1);    Thread t1 = new Thread(new Runnable() {      public void run() {        try {          latch.await();          Thread.sleep(1000);        } catch (Exception ex) {        }        bs.set(1);      }    });    Thread t2 = new Thread(new Runnable() {      public void run() {        try {          latch.await();          Thread.sleep(1000);        } catch (Exception e) {        }        bs.set(2);      }    });     t1.start();    t2.start();    latch.countDown();    t1.join();    t2.join();   // crucial part here:    System.out.println(bs.get(1));    System.out.println(bs.get(2));  }}

問題來了,這段代碼輸出的結果是什麼呢?它究竟能輸出什麼結果,上面的程式即使在崩潰的JVM上,仍然允許列印輸出什麼結果呢?

讓我們來看看這個程式做了什麼:

  •     初始化了一個BitSet對象
  •     兩個線程並行運行,分別對第一和第二位的欄位值設定為true
  •     我們嘗試讓這兩個線程同時運行。
  •     讀取BitSet對象的值,然後輸出結果。

接下來,我們需要構造一些測試案例來檢查這些行為。顯然,其中一個只能運行該例子,然後觀察結果,回答上面的問題,可是,回答第二個關於允許輸出的結果,需要些技巧。

熟能生巧

幸運的是,我們可以使用工具。 JCStress 就是一個為瞭解決這類問題而產生的測試載入器。

我們可以很容易地將我們的test case寫成JCStress可以識別的形式。事實上, 它已經為我們準備好了多種可能情況下的介面。我們需要一個例子,在這個例子中,2個線程並發地執行,執行的結果表示為2個布爾值。

我們使用一個Actor2_Arbiter1_Test<BitSet, BooleanResult2>介面, 它將為我們的2個線程提供一些方法塊和一個轉換方法,這個轉換方法將表示BitSet狀態的結果轉換成一對布爾值。我們需要找個 Java 8 JVM 來運行它, 但是現在這已經不是什麼問題了.

看下面的實現. 是不是特別簡潔?
 

public class AnExampleTest implements       Actor2_Arbiter1_Test<BitSet, BooleanResult2> {  @Override public void actor1(BitSet s, BooleanResult2 r) {  s.set(1); }  @Override public void actor2(BitSet s, BooleanResult2 r) {  s.set(2); }  @Override public void arbiter1(BitSet s, BooleanResult2 r) {  r.r1 = s.get(1);  r.r2 = s.get(2); }  @Override public BitSet newState() {  return new BitSet(); }  @Override public BooleanResult2 newResult() {  return new BooleanResult2(); }}


現在在運行這個測試的時候,控制會去嘗試各種花樣以求擷取驅動這些動作的因素的所有可能組合: 並行的或者非並行的, 有和無負載檢測的, 還有一行中進行許多許多次, 因此所有可能的結果都會被記錄到.

當你想知道你的並行代碼是如何運作的時候,這是比靠你自己去挖空心思想出所有細節更勝一籌的辦法.

此外,為了能利用到JCStress 約束帶來的全面性的便利,我們需要給它提供一個對可能結果的解釋. 要那樣做的話我們就需要使用如下所示的一個簡單的XML檔案.

 <test name="org.openjdk.jcstress.tests.custom.AnExampleTest">  <contributed-by>Oleg Shelajev</contributed-by>  <description>   Tests if BitSet works well without synchronization.  </description>  <case>   <match>[true, true]</match>   <expect>ACCEPTABLE</expect>   <description>    Seeing all updates intact.   </description>  </case>  <case>   <match>[true, false]</match>   <expect>ACCEPTABLE_INTERESTING</expect>   <description>    T2 overwrites T1 result.   </description>  </case>  <case>   <match>[false, true]</match>   <expect>ACCEPTABLE_INTERESTING</expect>   <description>    T1 overwrites T2 result.   </description>  </case>  <unmatched>   <expect>FORBIDDEN</expect>   <description>    All other cases are unexpected.   </description>  </unmatched> </test>

現在,我們已經準備好讓這頭野獸開始咆哮了. 通過使用下面的命令列運行測試.

java -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:-RestrictContended -jar tests-custom/target/jcstress.jar -t=".*AnExampleTest"

而我們所得到的結果是一份優雅的報告.

現在很清楚的是,我們不僅可以得到預期的結果,即兩個線程都已經設定了它們的位,也遇到了一個競爭條件,一個線程將覆蓋另一個線程的結果。

即使你看到發生了這種事情,也一定要有“山人自有妙計”的淡定心態,不是嗎?

順便說一下,如果你在思考如何修改這個代碼,答案是仔細閱讀 Javadoc 中的 BitSet 類,並意識到那並非是安全執行緒的,需要外部同步。這可以很容易地通過增加同步塊相關設定值來實現。
 

synchronized (bs) { bs.set(1);}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.