JMM測試利器-JCStress學習筆記

北冥有隻魚發表於2023-05-16

前言

我們前文提到,當我們對理論有一些瞭解,我們就渴望驗證,如果無法對理論進行驗證,那麼我們就可能對理論將信將疑,那對於Java領域的併發理論,一向是難以測試的,更何況除錯, 這不僅僅是我們的認知,OpenJDK的 者也是這麼認知的:

Circa 2013 (≈ JDK 8) , we suddenly realized there are no regular concurrency tests that ask hard questions about JMM conformance

大約在2013年,也就是JDK8釋出的時候,我們突然意識到沒有常規的併發測試可以針對JMM的一致性進行深入探究。

Attempts to contribute JMM tests to JCK were futile: probabilistic tests

嘗試將JMM測試貢獻給JCK是徒勞的:這是機率性測試。

JVMs are notoriously awkward to test: many platforms, many compilers, many compilation and runtime modes, dependence on runtime profile

JVM是出了名的難以測試, 涉及多個平臺,多個編譯器、多種編譯和執行時模式,以及依賴的配置檔案,所以測試起來比較麻煩。

這也就是JCStress誕生的原因,我們希望有一個測試框架對JMM進行測試,方便我們驗證我們的猜想是否正確。但是關於JCStress這一方面的資料一向少之又少,我期待自己是第一手資料的獲得者,於是我嘗試看JCStress給出的文件, 首先我來到了JCStress在GitHub上的倉庫, 看到了下面一段話:

In order to understand jcstress tests and maybe write your own, it might be useful to work through the jcstress-samples. The samples come in three groups:

透過JCStress-sample,可以幫助你理解JCStress測試,幫助你寫出自己的測試

  • APISample target to explain the jcstress API;

​ APISample 的目標是解釋jcstress的API

  • JMMSample target to explain the basics of Java Memory Model;

​ JMM的Sample的目標是解釋Java記憶體模型的基本概念

  • ConcurrencySample show the interesting concurrent behaviors of standard library.

​ 展示標準庫有趣的併發行為

fine,接下來我們就直接嘗試閱讀範例來學習一下JCStress是如何使用的。

開始閱讀

這些範例我們有兩種方式可以拿到:

<dependency>
    <groupId>org.openjdk.jcstress</groupId>
    <artifactId>jcstress-samples</artifactId>
    <version>0.3</version>
 </dependency>

我們首先閱讀的第一個類叫:

/*
    This is our first concurrency test. It is deliberately simplistic to show
    testing approaches, introduce JCStress APIs, etc.
    這是我們第一個併發測試,這個測試被刻意的做的很簡單,以便展示測試方法、介紹JCStress API等方法。
    Suppose we want to see if the field increment is atomic. 
    我們想看下欄位的遞增是否是原子性的.
    We can make test with two actors, both actors incrementing the field and recording what value they observed into the result     object. 
    我們可以用兩個actor進行測試,兩個actor都對欄位進行自增,並將觀察到的值記錄到result物件裡面
    As JCStress runs, it will invoke these methods on the objects holding the field once per each actor and instance, and record     what results are coming from there.
    當JCStress執行時,她會對每個"actor"和例項進行一次方法呼叫,並記錄呼叫的結果。
    Done enough times, we will get the history of observed results, and that
    would tell us something about the concurrent behavior. For example, running
    this test would yield:
    進行充足的測試之後, 我們將會得到觀察結果的歷史,將會告訴我們併發行為發生了什麼,執行這些測試將產生:
      [OK] o.o.j.t.JCStressSample_01_Simple
      (JVM args: [-server])  請求虛擬機器以server方式執行
      觀察狀態           發生的情況           期待              解釋    
      Observed state   Occurrences   Expectation  Interpretation
                                                     // 兩個執行緒得出了相同的值: 原子性測試失敗     
                1, 1    54,734,140    ACCEPTABLE  Both threads came up with the same value: atomicity failure.
                                                  // actor1 先新增  後actor2新增
                1, 2    47,037,891    ACCEPTABLE  actor1 incremented, then actor2.
                                                  // actor2 先增加, actor1後增加        
                2, 1    53,204,629    ACCEPTABLE  actor2 incremented, then actor1.
      如何執行JCStress測試,            
     How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t JCStress_APISample_01_Simple
 */
 標記這個類是需要JCStress測試的類
// Mark the class as JCStress test.
@JCStressTest
// These are the test outcomes.
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")
@Outcome(id = "1, 2", expect = Expect.ACCEPTABLE, desc = "actor1 incremented, then actor2.")
@Outcome(id = "2, 1", expect = Expect.ACCEPTABLE, desc = "actor2 incremented, then actor1.")
// 狀態物件     
// This is a state object
@State
public class APISample_01_Simple {
    
    int v;
    
    @Actor
    public void actor1(II_Result r) {
        // 將結果actor1的行為產生的記錄進r1
        // record result from actor1 to field r1 
        r.r1 = ++v; 
    }
    
    @Actor
    public void actor2(II_Result r) {
        //將actor2的結果記錄進r2
        // record result from actor2 to field r2
        r.r2 = ++v; 
    }
}

我們上面已經對這個測試示例進行了簡單的解讀,這裡我們要總結一下JCStress的工作模式 , 我們在類裡面宣告我們希望JCStress執行的行為,這些行為的結果被記錄到結果中, 最後和@Outcome註解中宣告的內容做對比, 輸出對應的結果:

iqv8hp.png

現在這段程式碼, 我們唯一還不明白作用的就是@outcome、@State、 @Actor。我們還是看原始碼上的註釋:

  • State

State is the central annotation for handling test state. It annotates the class that holds the data mutated/read by the tests.
Important properties for the class are:

State 是處理測試狀態的中心註解,有這個註解的類,在測試中負責讀取和修改資料。這些類要求以下兩個屬性

  1. State class should be public, non-inner class.

    State 類應當是public ,而不是內部類

  2. State class should have a default constructor.

​ State 類應當有一個預設的建構函式

During the run, many State instances are created, and therefore the tests should try to minimize state instance footprint. All actions in constructors and instance initializers are visible to all Actor and Arbiter threads.

在執行期間,有State註解的類會被大量建立,因此在測試中應當儘量減少State例項的佔用,所有建構函式中的操作和變數初始化程式碼塊對所有的Actor 和Arbiter 可見。

constructor 這個我是知道的,instance initializers 是 指直接對類的成員變數進行初始化的操作:

class Dog {
  private String name = "旺財";
  // 這種宣告 我是頭一次見  
  {name = "小小狗"}  
}
  • outcome

Outcome describes the test outcome, and how to deal with it. It is usually the case that a JCStressTest has multiple outcomes, each with its distinct id(). id() is cross-matched with Result-class' toString() value. id() allows regular expressions.

OutCome註解用於描述測試結果, 以及如何處理它。通常情況下,一個JCStress測試有多個輸出結果,每個結果都有一個唯一id 。id和觀測結果類的toString方法返回值相匹配,id允許正規表示式。

For example, this outcome captures all results where there is a trailing "1": \@Outcome(id = ".*, 1", ...) When there is no ambiguity in what outcome should match the result, the annotation order is irrelevant. When there is an ambiguity, the first outcome in the declaration order is matched. There can be a default outcome, which captures any non-captured result. It is the one with the default id().

例如,我們在id裡面填入的屬性為.*, 1,那麼將會匹配的字串形式為尾巴為, 1的結果,比如2,1。當預計結果與實際輸出結果匹配且沒有歧義,OutCome的註解順序是任意的。當預計結果與實際輸出結果有歧義時,指存在兩個OutCome中的id值存在交集,那麼將會匹配第一個註解。所以也可以設定一個預設的輸出結果,當實際輸出和預計輸出不匹配的時候,匹配預設輸出。

我們結合APISample_01_Simple的程式碼來解釋,注意在這個類裡面兩個方法將結果都放在了II_Result上,這是實際輸出,II_Result代表有兩個變數記錄值,JCStress裡面有各種各樣類似的類來記錄結果,II_Result代表提供了兩個變數來記錄實際執行結果,II代表兩個,那麼我們是可以大膽推導一下,有I_Result 、III_Result、IIII_Result。是的沒有錯,如果你覺得你需要測試的結果需要更多的變數來記錄,那麼需要變數的數量I_Result去JCStress裡面找就行了。 然後我們在看下I_Result 的toString方法:

//省略II_Result的其他方法和成員變數
@Result
public class II_Result implements Serializable {
    public String toString() {
        return "" + r1 + ", " + r2;
    }
}
//省略III_Result的其他方法和成員變數
@Result
public class IIII_Result implements Serializable {
    public String toString() {
        return "" + r1 + ", " + r2 + ", " + r3 + ", " + r4;
    }
}

我們會發現II_Result、IIII_Result的toString方法都是將成員變數用逗號拼接。那上面的例子我們就能大致看懂了,JCStress執行APISample_01_Simple,然後將結果和Outcome中的id做比較,如果相等會被記錄,expect屬性是什麼意思呢?對我們希望出現的結果進行評級,哪些是我們重點的結果,一共有四個等級:

ACCEPTABLE: Acceptable result. Acceptable results are not required to be present

可接受的結果,可接受的結果不要求出現

ACCEPTABLE_INTERESTING: Same as ACCEPTABLE, but this result will be highlighted in reports.

和ACCEPTABLE一樣,但是結果出現了就會在報告裡面被高亮。

FORBIDDEN: Forbidden result. Should never be present.

禁止出現的結果 應當永遠不出現

UNKNOWN: Internal expectation: no grading. Do not use.

未知,不要用
  • Actor

Actor is the central test annotation. It marks the methods that hold the actions done by the threads. The invariants that are maintained by the infrastructure are as follows:

Actor 是核心測試註解,被它標記的方法將會一個執行緒所執行,基礎架構會維護一組不變的條件:

  1. Each method is called only by one particular thread.

    每個方法會被一個特殊的執行緒所呼叫。

  2. Each method is called exactly once per State instance.

​ 每個方法被每個State例項所呼叫

Note that the invocation order against other Actor methods is deliberately not specified.

注意Actor所執行的方法呼叫順序是故意不指定的。

Therefore, two or more Actor methods may be used to model the concurrent execution on data held by State instance.

因此,可以使用兩個或多個Actor方法來模擬對State例項持有資料的併發執行。

Actor-annotated methods can have only the State or Result-annotated classes as the parameters.

只有被@Result或@State標記的類才能作為Actor方法(擁有Actor註解)的引數

Actor methods may declare to throw the exceptions, but actually throwing the exception would fail the test.

Actor方法可能宣告丟擲異常,但是實際上丟擲的異常會導致測試失敗。

這裡我來解讀一下,II_Result 這個類上就帶有@Result註解。現在我們要解讀的就是++v了,除了++v之外,我們熟悉的還有v++,如果現在問我這倆啥區別,我大機率回答的是++v 先執行自增,然後將自增後的值賦給v,對應的程式碼應該是像下面這樣:

v = v + 1;

那v++呢,先使用值,後遞增,我理解應該是像下面這樣:

v = v; 
v = v + 1;

我們日常使用比較多的應該就是v++了:

for(int v = 0 ; v < 10 ; v++){}

for迴圈的括號是個小程式碼塊,第一次執行的時候首先宣告瞭一個v = 0,然後判斷v是否小於10,如果小於執行v++,這個時候v的值仍然是0,然後走迴圈體裡面的內容。結合我們上面的理解,這似乎有些割裂,如果按照我對v++的理解,一條語句等價於兩條語句,那麼這兩條語句還分開執行,這看起來非常怪。如果我們將v++對一個變數進行賦值,然後這個變數拿到的就是自增之後的值,我們也可以對我們的理解打補丁,即在迴圈中兩條語句不是順序執行,先執行v = v,迴圈體裡面的內容執行完畢再執行v = v + 1。那再完善一下呢,i++事實上被稱為字尾表示式,語義為i+1,然後返回舊值。++i被稱為字首表示式, 語義為自增返回新值。乍一看好像是自增的,但是我們在細化一下,在自增之前都要讀取變數的值,所以無論是++v,還是v++,都需要先讀值再自增,所以這是兩個操作,在Java裡面不是原子的,這是我們的論斷,我們跑一下上面的例子,看下結果。

跑一下看一下結果

一般來說JCStress是要用命令列跑的,但是這對於新手不友好,有些概念還需要理解一下,但是IDEA有專門為JCStress製作的外掛,但這個外掛並沒有做到全版本的IDEA適配,目前適配的版本如下:

Aqua — 2023.1 (preview)

IntelliJ IDEA Educational — 2022.2 — 2022.2.2

IntelliJ IDEA Community — 2022.2 — 2023.1.2 (eap)

IntelliJ IDEA Ultimate — 2022.2 — 2023.1.2 (eap)

Android Studio — Flamingo | 2022.2.1 — Hedgehog | 2023.1.1 Canary 2

本來這個外掛跑的是好好的,但是我換了臺機器就發現好像有點問題,還是用命令列吧,首先我們去GitHub將原始碼下載下來:

https://github.com/openjdk/jcstress.git

編譯JCStress這個專案的master分支最好是JDK 17,我試了其他版本,發現都會有點奇奇怪怪的問題,我們先不必深究這些問題,先跑出來結果再說. 首先我們在命令列執行:

mvn clean install -DskipTests -T 1C

本篇的例子來自於JCStress tag中的0.3,但是跑JCStress測試的時候,發現0.3上好像有點水土不服,但提供的例子都是差不多,基本沒變化。這裡跑測試的時候,用的就是master分支下面的示例,我們來跑下示例:

java -jar jcstress-samples/target/jcstress.jar -t API_01_Simple

VZuzyU.jpeg

跑完測試之後,測試報告會會出現在results資料夾下面:

VZuGqy.jpeg

我們開啟看下測試報告:

VZuKyE.jpeg

執行的次數比我們想象的要多,1877050次。

例子解讀

API_02_Arbiters

Arbiter 意為衝裁。

/*
    Another flavor of the same test as APISample_01_Simple is using arbiters.
    另一個和APISample_01_Simple一樣味道的測試是使用仲裁者。
    
    Arbiters run after both actors, and therefore can observe the final result. 
    Arbiters方法在actor方法執行之後執行,因此可以觀察到最後的結果。

    This allows to observe the permanent atomicity failure after both actors  finished.
    在actor方法執行之後,Arbiters方法將會觀察到原子性失敗的結果    
    How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t API_02_Arbiters
        ...

      RESULT         SAMPLES     FREQ       EXPECT  DESCRIPTION
           1     888,569,404    6.37%  Interesting  One update lost: atomicity failure.
           2  13,057,720,260   93.63%   Acceptable  Actors updated independently.
 */

@JCStressTest

// These are the test outcomes.
@Outcome(id = "1", expect = ACCEPTABLE_INTERESTING, desc = "One update lost: atomicity failure.")
@Outcome(id = "2", expect = ACCEPTABLE,             desc = "Actors updated independently.")
@State
public class API_02_Arbiters {

    int v;

    @Actor
    public void actor1() {
        v++;
    }

    @Actor
    public void actor2() {
        v++;
    }

    @Arbiter
    public void arbiter(I_Result r) {
        r.r1 = v;
    }
}

API_03_Termination

/*
    Some concurrency tests are not following the "run continously" pattern.
    一些併發測試可能並不適用"持續執行"模式。
    One of interesting test groups is that asserts if the code had terminated after a signal.
    一個有趣的測試是確定程式碼是否在接收到訊號之後終止
    Here, we use a single @Actor that busy-waits on a field, and a @Signal that
    sets that field.
    在下面的例子中,我們使用@Actor來讓一個執行緒在一個欄位上忙等,然後用@Signal方法去設定這個欄位。
    JCStress would start actor, and then deliver the signal.
    JCStress會首先啟動actor,然後啟動signal.
    If it exits in reasonable time, it will record "TERMINATED" result, otherwise
    record "STALE".
    如果在一定的時間退出,它將會記錄"TERMINATED",否則將會記錄state。
    How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t API_03_Termination

        ...

          RESULT  SAMPLES     FREQ       EXPECT  DESCRIPTION
           STALE        1   <0.01%  Interesting  Test hung up.
      TERMINATED   13,168   99.99%   Acceptable  Gracefully finished.

      Messages:
        Have stale threads, forcing VM to exit for proper cleanup.
 */

@JCStressTest(Mode.Termination)
@Outcome(id = "TERMINATED", expect = ACCEPTABLE,             desc = "Gracefully finished.")
@Outcome(id = "STALE",      expect = ACCEPTABLE_INTERESTING, desc = "Test hung up.")
@State
public class API_03_Termination {

    @Actor
    public void actor1() throws InterruptedException {
        synchronized (this) {
            wait();
        }
    }

    @Signal
    public void signal() {
        synchronized (this) {
            notify();
        }
    }
}

這裡我們不認識的也就是 @Signal,我們看下這個類上的註釋:

/**
 * {@link Signal} is useful for delivering a termination signal to {@link Actor}  in {@link Mode#Termination} tests.
 * Signal 被用於 在中斷測試中傳送中斷訊號給Actor。
 *  It will run after {@link Actor} in question  started executing.
 *  它將會在相應的Actor執行之後開始執行。
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Signal {
}

API_04_Nesting

/*
It is sometimes convenient to put the tests in the same source file for better comparison. JCStress allows to nest tests like this:
有時候將一些測試放入一個原始檔可以更好的比較,JCStress允許將下面一樣巢狀

    How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t API_04_Nesting

        ...

        .......... [OK] org.openjdk.jcstress.samples.api.APISample_04_Nesting.PlainTest

          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION
            1, 1   21,965,585    4.5%  Interesting  Both actors came up with the same value: atomicity failure.
            1, 2  229,978,309   47.5%   Acceptable  actor1 incremented, then actor2.
            2, 1  232,647,044   48.0%   Acceptable  actor2 incremented, then actor1.

        .......... [OK] org.openjdk.jcstress.samples.api.APISample_04_Nesting.VolatileTest

          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION
            1, 1    4,612,990    1.4%  Interesting  Both actors came up with the same value: atomicity failure.
            1, 2   95,520,678   28.4%   Acceptable  actor1 incremented, then actor2.
            2, 1  236,498,350   70.3%   Acceptable  actor2 incremented, then actor1.
 */

public class API_04_Nesting {

    @JCStressTest
    @Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")
    @Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")
    @Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")
    @State
    public static class PlainTest {
        int v;

        @Actor
        public void actor1(II_Result r) {
            r.r1 = ++v;
        }

        @Actor
        public void actor2(II_Result r) {
            r.r2 = ++v;
        }
    }

    @JCStressTest
    @Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")
    @Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")
    @Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")
    @State
    public static class VolatileTest {
        volatile int v;

        @Actor
        public void actor1(II_Result r) {
            r.r1 = ++v;
        }

        @Actor
        public void actor2(II_Result r) {
            r.r2 = ++v;
        }
    }
}

API_05_SharedMetadata

/*
    In many cases, tests share the outcomes and other metadata. To common them,
    there is a special @JCStressMeta annotation that says to look up the metadata
    at another class.
    在一些情況下,測試需要共享輸出結果和其他後設資料,為了共享他們,有一個特殊的註解@JCStressMeta來指示其他類在另一個類裡面查詢後設資料。
    How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t API_05_SharedMetadata

        ...

        .......... [OK] org.openjdk.jcstress.samples.api.APISample_05_SharedMetadata.PlainTest

          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION
            1, 1    6,549,293    1.4%  Interesting  Both actors came up with the same value: atomicity failure.
            1, 2  414,490,076   90.0%   Acceptable  actor1 incremented, then actor2.
            2, 1   39,540,969    8.6%   Acceptable  actor2 incremented, then actor1.

        .......... [OK] org.openjdk.jcstress.samples.api.APISample_05_SharedMetadata.VolatileTest

          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION
            1, 1   15,718,942    6.1%  Interesting  Both actors came up with the same value: atomicity failure.
            1, 2  120,855,601   47.2%   Acceptable  actor1 incremented, then actor2.
            2, 1  119,393,635   46.6%   Acceptable  actor2 incremented, then actor1.
 */

@Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")
@Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")
@Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")
public class API_05_SharedMetadata {

    @JCStressTest
    @JCStressMeta(API_05_SharedMetadata.class)
    @State
    public static class PlainTest {
        int v;

        @Actor
        public void actor1(II_Result r) {
            r.r1 = ++v;
        }

        @Actor
        public void actor2(II_Result r) {
            r.r2 = ++v;
        }
    }

    @JCStressTest
    @JCStressMeta(API_05_SharedMetadata.class)
    @State
    public static class VolatileTest {
        volatile int v;

        @Actor
        public void actor1(II_Result r) {
            r.r1 = ++v;
        }

        @Actor
        public void actor2(II_Result r) {
            r.r2 = ++v;
        }
    }
}

我們首先來看一下 @JCStressMeta這個註解:

/**
 * {@link JCStressMeta} points to another class with test meta-information.
 *    @JCStressMeta註解將指向另一個包含測試元資訊的類。
 * <p>When placed over {@link JCStressTest} class, the {@link Description}, {@link Outcome},
 * {@link Ref}, and other annotations will be inherited from the pointed class. This allows
 * to declare the description, grading and references only once for a group of tests.</p>
 *  JCStressMeta 
 * 當它和@JCStressTest一起出現,@Description、@Outcome、@Ref等其他註解將會從指向的類繼承。這樣就可以在一組測試中只宣告一次描* 述、評分和參考資訊
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface JCStressMeta {

    Class value();

}

API_06_Descriptions

/*
    JCStress also allows to put the descriptions and references right at the test.
    This helps to identify the goal for the test, as well as the discussions about
    the behavior in question.
    JCStress 還允許將引用和描述放入測試中,這將幫我們認清測試目標, 以及相關問題的討論
    How to run this test:
       $ java -jar jcstress-samples/target/jcstress.jar -t API_06_Descriptions
 */

@JCStressTest

// Optional test description
@Description("Sample Hello World test")

// Optional references. @Ref is repeatable.
@Ref("http://openjdk.java.net/projects/code-tools/jcstress/")

@Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")
@Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")
@Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")
@State
public class API_06_Descriptions {

    int v;

    @Actor
    public void actor1(II_Result r) {
        r.r1 = ++v;
    }

    @Actor
    public void actor2(II_Result r) {
        r.r2 = ++v;
    }
}

總結一下

本篇我們介紹的JCStress註解有:

  • @JCStressTest 標定這個類需要被JCStress測試
  • 輸入引數 II_Result 用於記錄測試結果 ,這個型別的結果幾個I,就是由幾個變數,記錄的結果最後會被用逗號拼接,然後和outcome註解的id來匹配。
  • Outcome 的id屬性用於設定我們希望測出來的結果,expect 用於標定測試等級。
  • @Actor 標定的方法 會被一個執行緒執行,只有類上出現了@State,類上的方法才可以允許出現@Actor註解。
  • @Arbiter 在actor方法之後執行
  • @Signal 被用於在中斷測試中傳送中斷訊號給Actor。
  • 你也可以將一些測試放在一個類中
  • 有的時候 你想做的併發測試,預期輸出結果都一樣,我們可以用@JCStressMeta註解,共享屬性。
  • 有的時候我們也想和別人討論併發問題,並希望在測試輸出的文件中附帶參考連結和描述,我們可以用@Ref和@Description。

寫在最後

這個框架倒是我頭一次只看官方文件,根據例子寫的教程,探索的過程倒是摔了不少跟頭,剛開始不想用命令列,想用IDEA的一個外掛JCStress來跑,換了一臺電腦之後,發現這個外掛有點水土不服,本來想看下問題出在哪裡,但是想想要花不少的時間,這個想法也就打消了。其實本篇還打算介紹一下執行緒中斷機制,但是感覺這個內容一篇之內也介紹不完,所性也砍了,本來想解讀測試報告的時候,把JCStress的執行機制也順帶介紹一下,Java的兩個編譯器C1、C2。但是剛敲幾個字就發現也是不小的篇幅,所性也砍了。後面我們介紹JMM,將用JCStress來驗證我們的猜想。

參考資料

[1] Difference between pre-increment and post-increment in a loop? https://stackoverflow.com/questions/484462/difference-between-pre-increment-and-post-increment-in-a-loop

[2] Why is i++ not atomic? https://stackoverflow.com/questions/25168062/why-is-i-not-atomic

相關文章