preface

If you want to study high concurrency, you typically use high-concurrency tools for testing. JCStress (Java Concurrency Stress) is a high-concurrency testing tool in the OpenJDK that allows us to explore the JVM, libraries, and hardware under high-concurrency scenarios.

JCStress is easy to learn, and there are a number of test cases for high-concurrency scenarios that can be run by introducing a JAR package.

How to use JCStress

The core package is required. The sample package is not required. This is to demonstrate the examples.

<dependencies>
    <! -- JCStress Core package -->
    <dependency>
        <groupId>org.openjdk.jcstress</groupId>
        <artifactId>jcstress-core</artifactId>
        <version>0.3</version>
    </dependency>
    <! -- JCStress test case package
    <dependency>
        <groupId>org.openjdk.jcstress</groupId>
        <artifactId>jcstress-samples</artifactId>
        <version>0.3</version>
    </dependency>
</dependencies>
Copy the code

Let’s start with a simple test case, and if you don’t understand some of the notes, we’ll explain them later. This sample calls actor1 and actor2 once at high concurrency. By normal logic, the final value of x is either -1 or 5. If two lines of code in actor2 are reordered, the value of x may be 0.

package com.nobody;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;

/ * * *@DescriptionTest instruction reorder *@Author Mr.nobody
 * @Date 2021/4/6
 * @Version1.0 * /
@JCStressTest // Mark this class as a concurrent test class
@Outcome(id = {"0"}, expect = Expect.ACCEPTABLE_INTERESTING, desc = "wrong result") // Describe the test results
@Outcome(id = {"-1", "5"}, expect = Expect.ACCEPTABLE, desc = "normal result") // Describe the test results
@State // Mark this class as stateful
public class TestInstructionReorder {

    private boolean flag ;
    private int x;

    public TestInstructionReorder(a) {}

    @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; }}Copy the code

Configuration program of the Main class, org. Its. Jcstress. The Main is a startup jcstress classes; You can then set the -t parameter to specify the class to be tested. Of course, you can also specify the package name after -t to execute all test classes under the specified package. If the -t parameter is not specified, classes of all packages under the project are scanned by default.

Running the program, the result shows that the value of x appears 0, -1, 5, where the value of 0 is not what I expected, but it does occur under high concurrency, although the probability of occurrence is much lower than other values (hundreds of thousands of times) (more than 200 times).

Jmeter focuses on the overall response speed of the interface, while JCStress focuses on the high concurrency test of a certain piece of logic code, focusing more on the research of JVM, class library, etc.

Furthermore, JCStress takes into account tests under different JVM parameter Settings and automatically sets them up for us, as shown above [-xx: -tieredCompilation].

In addition to the test results displayed in the command line window, the Results folder will be generated in the directory where the project is located to generate the test results document, in which index.html is the test overview, other HTML files are the report of each test class, combined with the result data structure visualization graph is easier to understand.

JCStress notes

@JCStressTest

Marker for concurrent test class, one class. It has a org. Its jcstress. Annotations. The Mode attribute value of an enum type. Continuous Mode: runs several actors, Ariter threads, and collects statistics. The Mode.Termination Mode represents running a single Actor with blocking/loop operations to see if it responds to Singal signals.

@State

Mark a class to be stateful, that is, to have data that can be read or written to, such as x and falg in the above example. The State class must be public, not inner (it can be static inner), and must have a default constructor.

@Outcome

Describe the result of the test, it has three attributes, id attribute is a string array, represents the received result, support regular expression; Expect represents the expectation of an observation, and its value is an enumerated value; The desc attribute specifies a human-readable description of the result. The @outcomes annotation can combine multiple result annotations.

@Actor

@actor is a central test annotation that marks methods that can be called by a particular thread and only once per object. The order of multiple Actro method calls is not guaranteed, they are executed concurrently, and methods can throw exceptions and cause tests to fail. The Actor method class must have a State or Result annotation.

@Arbiter

Its function is actually similar to @actor, but the method call of the Arbiter mark is after all the method call of the @Actor mark, so the method it marks is generally used as the final result of collection.

@Signal

This annotation also marks the method, but it works in JCStressTest Termination mode, where it is called after all actors.

@Result

It is labeled class as a result of the test class, JCStress own org. Its. JCStress. Infra. Results under the package there is a lot of test results, different class can be used to keep the different results. For example, the I_Result class has a variable r1 of type int; The II_Result class has two variables of type int r1 and R2.

JCStress plug-in

There is a plug-in that integrates JCStress and Gradle, we just need to introduce this plug-in in build. Gradle and use the plug-in command to test. The plugin dependency is jcStress-gradle-plugin.

The build.gradle file is shown below. Different versions of the plugin integrate with the default JCStress version, although we can also customize the changes, as shown in the last line below.

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'jcstress'

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com. Making. 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}"
}
Copy the code

We can then write test classes in the project, such as the above example, and finally execute gradle jcStress in the project root directory to display the test results. You can also specify a class to test with a parameter, such as gradle jcStress –tests “TestInstructionReorder”.

Plugin source address: github.com/jerzykrlk/j…