并发测试工具: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

原文地址:https://www.cnblogs.com/wwjj4811/p/14310930.html