並發測試工具Jcstress使用教程
Jcstress 全稱 Java Concurrency Stress,是一種並發壓力測試工具,可以幫助研究JVM、java類庫和硬件中並發的正確性。
Wiki地址:https://wiki.openjdk.java.net/display/CodeTools/jcstress
Wiki中有一個Jcstress的example,可以根據example很方便入門使用jcstress。當然,我們也可以手動創建自己的Jcstress測試工程。
Maven例子
添加maven依賴
<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>
pom.xml 可以如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jfound</groupId>
<artifactId>jcstress-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
新增一個測試類,這次以測試變量加 volatile
和不加 volatile
的區別。代碼如下:
package jfound;
import org.openjdk.jcstress.annotations.*;
public class VolatileTest {
@JCStressTest(Mode.Termination)
@Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
@Outcome(id = "STALE", expect = Expect.ACCEPTABLE_INTERESTING)
@State
public static class NoVolatile {
private int i = 0;
@Actor
public void actor() {
while (i == 0) {
//nothing
}
}
@Signal
public void single() {
i = 1;
}
}
@JCStressTest(Mode.Termination)
@Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
@Outcome(id = "STALE", expect = Expect.FORBIDDEN)
@State
public static class AddVolatile {
private volatile int i = 0;
@Actor
public void actor() {
while (i == 0) {
//nothing
}
}
@Signal
public void single() {
i = 1;
}
}
}
VolatileTest
類里面有兩個靜態內部類,其中 NoVolatile
的 i 是沒有加 volatile
關鍵字的,AddVolatile
類中的 i 是有加 volatile
關鍵字的。
測試注解說明
- org.openjdk.jcstress.annotations.JCStressTest
該注解標記一個類為一個並發測試的類,有一個屬性 value 為 org.openjdk.jcstress.annotations.Mode
。mode
有Continuous 和 Termination 兩種模式。Continuous 模式是運行幾個 org.openjdk.jcstress.annotations.Actor
、org.openjdk.jcstress.annotations.Ariter
線程,並收集結果。Termination 模式運行具有阻塞/循環操作的單個 Actor
和 Singal
的測試。
- org.openjdk.jcstress.annotations.Outcome
Outcome
是描述測試結果,並處理這個結果,該注解有 id、expect、desc 這三個屬性。
其中 id 接收結果,如上面的 TERMINATED 、 STALE 就是結果,id 還支持正則表達式;expect 是期望處理結果,類型為 org.openjdk.jcstress.annotationsExpect
,有 ACCEPTABLE、ACCEPTABLE_INTERESTING 、FORBIDDEN、UNKNOWN 四種類型,ACCEPTABLE 表示接受當前結果,ACCEPTABLE 結果不一定會存在;ACCEPTABLE_INTERESTING 和 ACCEPTABLE 差不多,唯一不一樣的是,這個結果會在生成的報告中高亮;FORBIDDEN 表示永遠不應該出現的結果,若測試過程中有該結果,意味着測試失敗; UNKNOWN 沒有評分,不使用。
如上面例子所示,一個測試類 (JCStressTest) 中可以有一個或多個 Outcome
。
- org.openjdk.jcstress.annotations.State
標記這個類是有狀態的,有狀態的意識是擁有數據,而且數據是可以被修改的,如上面測試例子中的 i , 其中 i 就是擁有的數據。為了方便理解,可以拿 Stateless 來做對比;如一些實體類是有狀態(State)的,Service理應設置為無狀態的 Sateless
,盡管service有屬性,但是屬性也是不能被修改的。
State
修飾的類必須是 public 的,不能是內部類,但是可以是靜態內部類,如上面例子。
State
修飾的類必須有一個默認構造函數
- org.openjdk.jcstress.annotations.Actor
該注解標記的方法會被線程調用,被 Actor 修飾方法所在的類必須有 State
或者 Result
注解,被其修飾的方法可以拋出異常,但是拋出異常的話,會引起測試失敗。注意的是,Actor 標記的每個方法僅由一個特定線程調用,而且每個被 State 標記的實例僅調用每一個方法 。Actor 修飾的方法之間是沒有順序的,調用是並發執行的。
與之相對的還有 org.openjdk.jcstress.annotations.Arbiter
注解,Arbiter
注解和 Actor
注解差不多,不一樣的是 Arbiter
注解聲明的方法訪問是在所有 State
之后,而且 Actor
所有的內存都對 Arbiter
可見,這就使得 Arbiter
在確認最終狀態信息上有很大的作用。
- org.openjdk.jcstress.annotations.Singal
改注解在 Termination
模式下是比較有用的,它標記的方法執行是在 Actor
標記方法執行之后。
- org.openjdk.jcstress.annotations.Result
用改注解標記的類是測試結果的類,如 org.openjdk.jcstress.infra.results
下的類,該注解標記的類所有 field 都必須是原生數據類似或者是 String 類型,所有 field 都應該是 public。可以參考 org.openjdk.jcstress.infra.results
下的類。用法如下
@Actor
public void actor1(I_Result r) {
r.r1 = 1;
}
把測試結果用 Result
修飾的類接收,通常是配合 toString
打印出來。
- org.openjdk.jcstress.annotations.Description
與測試無關,可以定義一些描述,方便查看
- org.openjdk.jcstress.annotations.Ref
與測試無關,提供一些引用,如地址,ISBN等等。
- org.openjdk.jcstress.annotations.JCStressMeta
Description
、Outcome
、Ref
這些注解是可以放到一個公共類,然后由 JCStressMeta 注解引進來,以達到重復使用的目的。
運行及結果說明
在idea下執行如下
其中 Main class
填寫 org.org.openjdk.jcstress.Main
, 參數中的 -t
是指指定測試包名,不指定的話是默認掃描項目所有包。
在執行完成后,會在項目所在的目錄下生成 results
文件夾,里面有個 index.html
,用瀏覽器打開即可查看報告。
總結
本文以一個例子展開來編寫 Jcstress 並發測試工具的使用說明,並描述了 Jcstress 的所有核心注解,方便初學者去了解並借助該工具來學習並發編程。