Java高併發測試框架JCStress

陳皮的JavaLib發表於2021-04-07

前言

如果要研究高併發,一般會藉助高併發工具來進行測試。JCStress(Java Concurrency Stress)它是OpenJDK中的一個高併發測試工具,它可以幫助我們研究在高併發場景下JVM,類庫以及硬體等狀況。

JCStress學起來很簡單,而且官方也提供了許多高併發場景下的測試用例,只要引入一個jar包,即可執行研究。

如何使用JCStress

此演示用maven工程,首先需要引入jar包,核心包是必須要的,樣例包非必須要,此是為了演示其中的例子。

<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。

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,類庫等領域的研究。

而且,JCStress會考慮不同JVM引數設定下的測試,而且自動幫我們設定,例如上圖所示[-XX:-TieredCompilation]。

除了命令列視窗顯示的測試結果之外,還會在專案所在的目錄下生成 results資料夾,生成測試結果文件,其中index.html是測試總覽,其他html檔案是每個測試類的報告,結合結果資料結構視覺化圖形更加容易理解。

JCStress 註解說明

@JCStressTest

標記一個類為併發測試的類,它有一個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 外掛

有一個外掛整合了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"

外掛原始碼地址:https://github.com/jerzykrlk/jcstress-gradle-plugin

相關文章