若是要研究高併發,通常會藉助高併發工具來進行測試。JCStress(Java Concurrency Stress)它是OpenJDK中的一個高併發測試工具,它能夠幫助咱們研究在高併發場景下JVM,類庫以及硬件等情況。html
JCStress學起來很簡單,並且官方也提供了許多高併發場景下的測試用例,只要引入一個jar包,便可運行研究。java
此演示用maven工程,首先須要引入jar包,核心包是必需要的,樣例包非必需要,此是爲了演示其中的例子。git
<dependencies> <!-- jcstress 核心包 --> <dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>0.3</version> </dependency> <!-- jcstress測試用例包 --> <dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-samples</artifactId> <version>0.3</version> </dependency> </dependencies>
先寫一個簡單測試用例,一些註解不明白能夠先無論,後面會講解。此樣例會在高併發下調用actor1和actor2方法各一次,按照正常邏輯,x最後的值要麼是-1要麼是5,若是actor2方法內的2行代碼發生了指令重排序,就會致使x的值可能爲0。github
package com.nobody; import org.openjdk.jcstress.annotations.*; import org.openjdk.jcstress.infra.results.I_Result; /** * @Description 測試指令重排序 * @Author Mr.nobody * @Date 2021/4/6 * @Version 1.0 */ @JCStressTest // 標記此類爲一個併發測試類 @Outcome(id = {"0"}, expect = Expect.ACCEPTABLE_INTERESTING, desc = "wrong result") // 描述測試結果 @Outcome(id = {"-1", "5"}, expect = Expect.ACCEPTABLE, desc = "normal result") // 描述測試結果 @State //標記此類是有狀態的 public class TestInstructionReorder { private boolean flag ; private int x; public TestInstructionReorder() {} @Actor public void actor1(I_Result r) { if (flag) { r.r1 = x; } else { r.r1 = -1; } } @Actor public void actor2(I_Result r) { this.x = 5; flag = true; } }
配置程序的主類,org.openjdk.jcstress.Main
是JCStress自帶的一個啓動類;而後能夠配置-t參數設置須要測試的類,固然 -t 後面也能夠指定包名,表示執行指定包下的全部測試類。若是不指定-t參數,默認會掃描項目下全部包的類。正則表達式
運行程序,結果顯示,x的值出現了0,-1,5三種結果,其中值爲0不是我期待的,可是它在高併發下確實出現了,雖然相比其餘值(幾十萬次)出現的機率(200屢次)很低。數組
有些人會說用jmeter工具不也能夠測試高併發,可是它們的側重點仍是不同的,jmeter側重對於接口總體的響應速度等進行測試,而JCStress框架能對某塊邏輯代碼進行高併發測試,更加側重JVM,類庫等領域的研究。bash
並且,JCStress會考慮不一樣JVM參數設置下的測試,並且自動幫咱們設置,例如上圖所示[-XX:-TieredCompilation]。數據結構
除了命令行窗口顯示的測試結果以外,還會在項目所在的目錄下生成 results文件夾,生成測試結果文檔,其中index.html是測試總覽,其餘html文件是每一個測試類的報告,結合結果數據結構可視化圖形更加容易理解。併發
@JCStressTestapp
標記一個類爲併發測試的類,它有一個org.openjdk.jcstress.annotations.Mode枚舉類型的屬性value。Mode.Continuous模式表示會運行幾個Actor,Ariter線程,並收集統計結果。Mode.Termination模式表明運行具備阻塞/循環操做的單個Actor,看是否響應Singal信號。
@State
標記一個類是有狀態的,即擁有能夠讀寫的數據,例如上例的x和falg。State類只能是public的,不能是內部類(能夠是靜態內部類),而且得有一個默認構造方法。
@Outcome
描述測試的結果,它有3個屬性,id屬性爲一個字符串數組,表示接收的結果,支持正則表達式;expect表示對觀測結果的指望,它的值是一個枚舉值;desc屬性指定一個易於人類理解的對結果的描述。@Outcomes註解能夠組合多個結果註解。
@Actor
@Actor是一箇中心測試註解,它標記的方法會被一個特定的線程調用,每個對象的方法只能被調用一次。多個Actro方法調用順序是不保證的,它們是併發執行的,方法能夠拋出異常而且會致使測試失敗。Actor方法所在的類必須有State或者Result註解。
@Arbiter
它的做用其實和@Actor差很少,可是Arbiter標記的方法調用是在全部@Actor標記的方法調用以後,因此它標記的方法通常做爲收集最後的結果來使用。
@Signal
此註解也是標記方法的,可是它是在JCStressTest的Termination模式下工做的,它的調用是在全部Actor以後。
@Result
它標記的類被做爲測試結果的類,JCStress自帶的org.openjdk.jcstress.infra.results包下就有大量的測試結果類,不一樣的類能夠用來保持不一樣的結果。例如I_Result類有一個int類型的變量r1;II_Result類有2個int類型的變量r1和r2。
有一個插件集成了JCStress和Gradle,咱們只須要在build.gradle中引入此插件,便可使用插件命令來進行測試。插件依賴爲jcstress-gradle-plugin。
build.gradle文件以下,不一樣版本的插件集成了默認的JCStress版本,固然咱們也能夠自定義更改,以下最後一行所示。
apply plugin: 'java' apply plugin: 'idea' apply plugin: 'jcstress' buildscript { repositories { jcenter() } dependencies { classpath 'com.github.erizo.gradle:jcstress-gradle-plugin:0.8.1' } } ext { jcstressVersion = '0.7' } repositories { jcenter() } dependencies { compile "org.openjdk.jcstress:jcstress-core:${jcstressVersion}" } jcstress { jcstressDependency "org.openjdk.jcstress:jcstress-core:${jcstressVersion}" }
而後在項目便可編寫測試類,例如仍是上面那個例子,最後咱們在項目根目錄下執行 gradle jcstress
,便可顯示測試結果。也能夠經過參數指定要測試的類,例如 gradle jcstress --tests "TestInstructionReorder"
。