簡介
jcstress:全名The Java Concurrency Stress tests,是一個實驗工具和一套測試工具,用於幫助研究JVM、類庫和硬件中並發支持的正確性。
官方github:https://github.com/openjdk/jcstress
官方給出的jcstress使用案例:http://hg.openjdk.java.net/code-tools/jcstress/file/tip/jcstress-samples/src/main/java/org/openjdk/jcstress/samples
依賴
<dependency>
<groupId>org.openjdk.jcstress</groupId>
<artifactId>jcstress-core</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>org.openjdk.jcstress</groupId>
<artifactId>jcstress-samples</artifactId>
<version>0.5</version>
</dependency>
HelloWorld
@JCStressTest
@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.")
@State
public class APISample_01_Simple {
int v;
@Actor
public void actor1(II_Result r) {
r.r1 = ++v; // record result from actor1 to field r1
}
@Actor
public void actor2(II_Result r) {
r.r2 = ++v; // record result from actor2 to field r2
}
}
是不是一臉懵逼?一堆不認識的注解,且聽我細細道來。
@JCStressTest:
該注解標記一個類為一個並發測試的類,有一個屬性 value 為Mode。mode 有Continuous 和 Termination 兩種模式。Continuous 模式是運行幾個 Actor 、Ariter 線程,並收集結果。Termination 模式運行具有阻塞/循環操作的單個 Actor 和 Singal 的測試。
@Outcome:
描述測試結果,並處理這個結果,該注解有 id、expect、desc 這三個屬性。其中 id 接收結果,id 還支持正則表達式;expect 是期望處理結果,有 ACCEPTABLE、ACCEPTABLE_INTERESTING 、FORBIDDEN、UNKNOWN 四種類型,ACCEPTABLE 表示接受當前結果,ACCEPTABLE 結果不一定會存在;ACCEPTABLE_INTERESTING 和 ACCEPTABLE 差不多,唯一不一樣的是,這個結果會在生成的報告中高亮;FORBIDDEN 表示永遠不應該出現的結果,若測試過程中有該結果,意味着測試失敗; UNKNOWN 沒有評分,不使用。
@State:
標記這個類是有狀態的,有狀態的意識是擁有數據,而且數據是可以被修改的,如上面測試例子中的 v , 其中 v 就是擁有的數據。State 修飾的類必須是 public 的,不能是內部類,但是可以是靜態內部類,如上面例子。State 修飾的類必須有一個默認構造函數
@Actor:
該注解標記的方法會被線程調用,被 Actor 修飾方法所在的類必須有 State 或者 Result注解,被其修飾的方法可以拋出異常,但是拋出異常的話,會引起測試失敗。注意的是,Actor 標記的每個方法僅由一個特定線程調用,而且每個被 State 標記的實例僅調用每一個方法 。Actor 修飾的方法之間是沒有順序的,調用是並發執行的。
那么如何運行呢?並沒有發現有運行的入口。
配置idea:
添加一個application
這里配置需要注意:main class必須寫成org.openjdk.jcstress.Main
,此外需要配置包名 -t package path
,package path具體和你的測試類所在的包一樣。
配置完成后,運行,等待一小段時間。
運行成功后,在我們項目根路徑下生成一個results文件夾,打開里面的index.html可以查看測試結果
@Result
在上一個案例中,我們使用II_Result類去收集兩個線程返回的結果(int類型),並在Outcome注解的id屬性中列舉了所有可能出現的情況。那我們如果想要收益一個int的返回值,或者收集float、char、byte等其他基本類型和Object類型,應該用什么類?
實際上jcstress已經在org.openjdk.jcstress.infra.results包下列舉了很多返回值類型供我們在平時開發測試中使用。

我們可以使用@Result注解自定義返回值類型,如下圖,該注解標記的類所有 field 都必須是原生數據類似或者是 String 類型,所有 field 都應該是 publi。不過需要自定義的情況幾乎沒有
@Arbiter
Arbiter 注解和 Actor 注解差不多,不一樣的是 Arbiter 注解聲明的方法運行在所有 Actor之后,而且 Actor 所有的內存都對 Arbiter 可見,這就使得 Arbiter 在確認最終狀態信息上有很大的作用
@JCStressTest
// These are the test outcomes.
@Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "One update lost: atomicity failure.")
@Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Actors updated independently.")
@State
public class APISample_02_Arbiters {
int v;
@Actor
public void actor1() {
v++;
}
@Actor
public void actor2() {
v++;
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = v;
}
}
@Signal
一些並發測試沒有遵循Mode.Continuous。一個有趣的測試組之一是斷言代碼是否在一個信號。
在這里,我們使用一個@Actor,等待一個字段,以及一個@Signal設置該字段。JCStress會啟動actor,然后傳遞信號。
如果在合理時間內退出,則記錄“TERMINATED”結果,否則記錄“STALE”。
public class APISample_03_Termination {
@JCStressTest(Mode.Termination)
@Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE, desc = "Gracefully finished.")
@Outcome(id = "STALE", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Test hung up.")
@State
public static class NotHasVolatile{
int v;
@Actor
public void actor1() {
while (v == 0) {
// spin
}
}
@Signal
public void signal() {
v = 1;
}
}
@JCStressTest(Mode.Termination)
@Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE, desc = "Gracefully finished.")
@Outcome(id = "STALE", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Test hung up.")
@State
public static class HasVolatile{
volatile int v;
@Actor
public void actor1() {
while (v == 0) {
// spin
}
}
@Signal
public void signal() {
v = 1;
}
}
}
有volatile關鍵字:
無volatile關鍵字:
測試結果符合預期。
@ JCStressMeta元數據共享
Description 、Outcome、Ref 這些注解是可以放到一個公共類,然后由@ JCStressMeta 注解引進來,以達到重復使用的目的。
@Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE, desc = "Gracefully finished.")
@Outcome(id = "STALE", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Test hung up.")
public class APISample_05_SharedMetadata {
@JCStressTest(Mode.Termination)
@JCStressMeta(APISample_05_SharedMetadata.class)
@State
public static class NotHasVolatile{
int v;
@Actor
public void actor1() {
while (v == 0) {
// spin
}
}
@Signal
public void signal() {
v = 1;
}
}
@JCStressTest(Mode.Termination)
@JCStressMeta(APISample_05_SharedMetadata.class)
@State
public static class HasVolatile{
volatile int v;
@Actor
public void actor1() {
while (v == 0) {
// spin
}
}
@Signal
public void signal() {
v = 1;
}
}
}
Descriptions描述信息
使用@Description和@Ref可以描述測試信息。只需要在測試類的上標注即可,對實際測試結果並無影響。
實戰:AtomicInteger原子性測試
@JCStressTest
@Outcome(id = "1", expect = Expect.FORBIDDEN, desc = "One update lost.")
@Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both updates.")
@State
public class AtomicIncrementTest {
AtomicInteger ai = new AtomicInteger();
@Actor
public void actor1() {
ai.incrementAndGet();
}
@Actor
public void actor2() {
ai.incrementAndGet();
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = ai.get();
}
}
並發測試結果:
由圖中結果Both updates,可知AtomicInteger的incrementAndGet具有原子性。