Java高并发测试框架JCStress详解
作者:陈皮的JavaLib
前言
如果要研究高并发,一般会借助高并发工具来进行测试。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
到此这篇关于Java高并发测试框架JCStress的文章就介绍到这了,更多相关Java高并发JCStress内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!