並發測試工具:jcstress


簡介

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 有ContinuousTermination 兩種模式。Continuous 模式是運行幾個 Actor 、Ariter 線程,並收集結果。Termination 模式運行具有阻塞/循環操作的單個 Actor 和 Singal 的測試。

@Outcome:

描述測試結果,並處理這個結果,該注解有 idexpectdesc 這三個屬性。其中 id 接收結果,id 還支持正則表達式;expect 是期望處理結果,有 ACCEPTABLEACCEPTABLE_INTERESTINGFORBIDDENUNKNOWN 四種類型,ACCEPTABLE 表示接受當前結果,ACCEPTABLE 結果不一定會存在;ACCEPTABLE_INTERESTING 和 ACCEPTABLE 差不多,唯一不一樣的是,這個結果會在生成的報告中高亮;FORBIDDEN 表示永遠不應該出現的結果,若測試過程中有該結果,意味着測試失敗; UNKNOWN 沒有評分,不使用。

@State:

標記這個類是有狀態的,有狀態的意識是擁有數據,而且數據是可以被修改的,如上面測試例子中的 v , 其中 v 就是擁有的數據。State 修飾的類必須是 public 的,不能是內部類,但是可以是靜態內部類,如上面例子。State 修飾的類必須有一個默認構造函數

@Actor:

該注解標記的方法會被線程調用,被 Actor 修飾方法所在的類必須有 State 或者 Result注解,被其修飾的方法可以拋出異常,但是拋出異常的話,會引起測試失敗。注意的是,Actor 標記的每個方法僅由一個特定線程調用,而且每個被 State 標記的實例僅調用每一個方法Actor 修飾的方法之間是沒有順序的,調用是並發執行的。

那么如何運行呢?並沒有發現有運行的入口。

配置idea:

添加一個application

image-20210121212949920

這里配置需要注意:main class必須寫成org.openjdk.jcstress.Main,此外需要配置包名 -t package path,package path具體和你的測試類所在的包一樣。

image-20210121213428818

配置完成后,運行,等待一小段時間。

image-20210121213318019

運行成功后,在我們項目根路徑下生成一個results文件夾,打開里面的index.html可以查看測試結果

image-20210121213606191

image-20210121214039399

@Result

在上一個案例中,我們使用II_Result類去收集兩個線程返回的結果(int類型),並在Outcome注解的id屬性中列舉了所有可能出現的情況。那我們如果想要收益一個int的返回值,或者收集float、char、byte等其他基本類型和Object類型,應該用什么類?

實際上jcstress已經在org.openjdk.jcstress.infra.results包下列舉了很多返回值類型供我們在平時開發測試中使用。

image-20210121222106002

我們可以使用@Result注解自定義返回值類型,如下圖,該注解標記的類所有 field 都必須是原生數據類似或者是 String 類型,所有 field 都應該是 publi。不過需要自定義的情況幾乎沒有

image-20210121222457487

@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;
    }

}

image-20210121214611754

@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關鍵字:

image-20210121215834648

無volatile關鍵字:

image-20210121215858034

測試結果符合預期。

@ 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可以描述測試信息。只需要在測試類的上標注即可,對實際測試結果並無影響。

image-20210121220611849

實戰: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();
    }
}

並發測試結果:

image-20210121221014747

由圖中結果Both updates,可知AtomicInteger的incrementAndGet具有原子性。

部分參考:https://www.cnblogs.com/jfound/p/12975617.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM