前言

咱们前文提到,当咱们对实践有一些理解,咱们就渴望验证,如果无奈对实践进行验证,那么咱们就可能对实践将信将疑,那对于Java畛域的并发实践,一贯是难以测试的,更何况调试, 这不仅仅是咱们的认知,OpenJDK的 者也是这么认知的:

Circa 2013 (≈ JDK 8) , we suddenly realized there are no regular concurrency tests that ask hard questions about JMM conformance

大概在2013年,也就是JDK8公布的时候,咱们忽然意识到没有惯例的并发测试能够针对JMM的一致性进行深刻探索。

Attempts to contribute JMM tests to JCK were futile: probabilistic tests

尝试将JMM测试奉献给JCK是徒劳的:这是概率性测试。

JVMs are notoriously awkward to test: many platforms, many compilers, many compilation and runtime modes, dependence on runtime profile

JVM是出了名的难以测试, 波及多个平台,多个编译器、多种编译和运行时模式,以及依赖的配置文件,所以测试起来比拟麻烦。

这也就是JCStress诞生的起因,咱们心愿有一个测试框架对JMM进行测试,不便咱们验证咱们的猜测是否正确。然而对于JCStress这一方面的材料一贯少之又少,我期待本人是第一手材料的获得者,于是我尝试看JCStress给出的文档, 首先我来到了JCStress在GitHub上的仓库, 看到了上面一段话:

In order to understand jcstress tests and maybe write your own, it might be useful to work through the jcstress-samples. The samples come in three groups:

通过JCStress-sample,能够帮忙你了解JCStress测试,帮忙你写出本人的测试

  • APISample target to explain the jcstress API;

APISample 的指标是解释jcstress的API

  • JMMSample target to explain the basics of Java Memory Model;

JMM的Sample的指标是解释Java内存模型的基本概念

  • ConcurrencySample show the interesting concurrent behaviors of standard library.

展现规范库乏味的并发行为

fine,接下来咱们就间接尝试浏览范例来学习一下JCStress是如何应用的。

开始浏览

这些范例咱们有两种形式能够拿到:

  • 从Github上下载一下JCStress的源码 , 地址如下: https://github.com/openjdk/jcstress.git
  • maven仓库间接获取对应的依赖:
<dependency>    <groupId>org.openjdk.jcstress</groupId>    <artifactId>jcstress-samples</artifactId>    <version>0.3</version> </dependency>

咱们首先浏览的第一个类叫:

/*    This is our first concurrency test. It is deliberately simplistic to show    testing approaches, introduce JCStress APIs, etc.    这是咱们第一个并发测试,这个测试被刻意的做的很简略,以便展现测试方法、介绍JCStress API等办法。    Suppose we want to see if the field increment is atomic.     咱们想看下字段的递增是否是原子性的.    We can make test with two actors, both actors incrementing the field and recording what value they observed into the result     object.     咱们能够用两个actor进行测试,两个actor都对字段进行自增,并将察看到的值记录到result对象外面    As JCStress runs, it will invoke these methods on the objects holding the field once per each actor and instance, and record     what results are coming from there.    当JCStress运行时,她会对每个"actor"和实例进行一次办法调用,并记录调用的后果。    Done enough times, we will get the history of observed results, and that    would tell us something about the concurrent behavior. For example, running    this test would yield:    进行短缺的测试之后, 咱们将会失去察看后果的历史,将会通知咱们并发行为产生了什么,运行这些测试将产生:      [OK] o.o.j.t.JCStressSample_01_Simple      (JVM args: [-server])  申请虚拟机以server形式运行      察看状态           产生的状况           期待              解释          Observed state   Occurrences   Expectation  Interpretation                                                     // 两个线程得出了雷同的值: 原子性测试失败                     1, 1    54,734,140    ACCEPTABLE  Both threads came up with the same value: atomicity failure.                                                  // actor1 先新增  后actor2新增                1, 2    47,037,891    ACCEPTABLE  actor1 incremented, then actor2.                                                  // actor2 先减少, actor1后减少                        2, 1    53,204,629    ACCEPTABLE  actor2 incremented, then actor1.      如何运行JCStress测试,                 How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t JCStress_APISample_01_Simple */ 标记这个类是须要JCStress测试的类// Mark the class as JCStress test.@JCStressTest// These are the test outcomes.@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.")// 状态对象     // This is a state object@Statepublic class APISample_01_Simple {        int v;        @Actor    public void actor1(II_Result r) {        // 将后果actor1的行为产生的记录进r1        // record result from actor1 to field r1         r.r1 = ++v;     }        @Actor    public void actor2(II_Result r) {        //将actor2的后果记录进r2        // record result from actor2 to field r2        r.r2 = ++v;     }}

咱们下面曾经对这个测试示例进行了简略的解读,这里咱们要总结一下JCStress的工作模式 , 咱们在类外面申明咱们心愿JCStress执行的行为,这些行为的后果被记录到后果中, 最初和@Outcome注解中申明的内容做比照, 输入对应的后果:

当初这段代码, 咱们惟一还不明确作用的就是@outcome、@State、 @Actor。咱们还是看源码上的正文:

  • State

State is the central annotation for handling test state. It annotates the class that holds the data mutated/read by the tests.
Important properties for the class are:

State 是解决测试状态的核心注解,有这个注解的类,在测试中负责读取和批改数据。这些类要求以下两个属性

  1. State class should be public, non-inner class.

    State 类该当是public ,而不是外部类

  2. State class should have a default constructor.

State 类该当有一个默认的构造函数

During the run, many State instances are created, and therefore the tests should try to minimize state instance footprint. All actions in constructors and instance initializers are visible to all Actor and Arbiter threads.

在运行期间,有State注解的类会被大量创立,因而在测试中该当尽量减少State实例的占用,所有构造函数中的操作和变量初始化代码块对所有的Actor 和Arbiter 可见。

constructor 这个我是晓得的,instance initializers 是 指间接对类的成员变量进行初始化的操作:

class Dog {  private String name = "旺财";  // 这种申明 我是头一次见    {name = "小小狗"}  }
  • outcome

Outcome describes the test outcome, and how to deal with it. It is usually the case that a JCStressTest has multiple outcomes, each with its distinct id(). id() is cross-matched with Result-class' toString() value. id() allows regular expressions.

OutCome注解用于形容测试后果, 以及如何解决它。通常状况下,一个JCStress测试有多个输入后果,每个后果都有一个惟一id 。id和观测后果类的toString办法返回值相匹配,id容许正则表达式。

For example, this outcome captures all results where there is a trailing "1": \@Outcome(id = ".*, 1", ...) When there is no ambiguity in what outcome should match the result, the annotation order is irrelevant. When there is an ambiguity, the first outcome in the declaration order is matched. There can be a default outcome, which captures any non-captured result. It is the one with the default id().

例如,咱们在id外面填入的属性为.*, 1,那么将会匹配的字符串模式为尾巴为, 1的后果,比方2,1。当预计后果与理论输入后果匹配且没有歧义,OutCome的注解程序是任意的。当预计后果与理论输入后果有歧义时,指存在两个OutCome中的id值存在交加,那么将会匹配第一个注解。所以也能够设置一个默认的输入后果,当理论输入和预计输入不匹配的时候,匹配默认输入。

咱们联合APISample_01_Simple的代码来解释,留神在这个类外面两个办法将后果都放在了II_Result上,这是理论输入,II_Result代表有两个变量记录值,JCStress外面有各种各样相似的类来记录后果,II_Result代表提供了两个变量来记录理论运行后果,II代表两个,那么咱们是能够大胆推导一下,有I_Result 、III_Result、IIII_Result。是的没有错,如果你感觉你须要测试的后果须要更多的变量来记录,那么须要变量的数量I_Result去JCStress外面找就行了。 而后咱们在看下I_Result 的toString办法:

//省略II_Result的其余办法和成员变量@Resultpublic class II_Result implements Serializable {    public String toString() {        return "" + r1 + ", " + r2;    }}//省略III_Result的其余办法和成员变量@Resultpublic class IIII_Result implements Serializable {    public String toString() {        return "" + r1 + ", " + r2 + ", " + r3 + ", " + r4;    }}

咱们会发现II_Result、IIII_Result的toString办法都是将成员变量用逗号拼接。那下面的例子咱们就能大抵看懂了,JCStress执行APISample_01_Simple,而后将后果和Outcome中的id做比拟,如果相等会被记录,expect属性是什么意思呢?对咱们心愿呈现的后果进行评级,哪些是咱们重点的后果,一共有四个等级:

ACCEPTABLE: Acceptable result. Acceptable results are not required to be present

可承受的后果,可承受的后果不要求呈现

ACCEPTABLE_INTERESTING: Same as ACCEPTABLE, but this result will be highlighted in reports.

和ACCEPTABLE一样,然而后果呈现了就会在报告外面被高亮。

FORBIDDEN: Forbidden result. Should never be present.

禁止呈现的后果 该当永远不呈现

UNKNOWN: Internal expectation: no grading. Do not use.

未知,不要用
  • Actor

Actor is the central test annotation. It marks the methods that hold the actions done by the threads. The invariants that are maintained by the infrastructure are as follows:

Actor 是外围测试注解,被它标记的办法将会一个线程所执行,基础架构会保护一组不变的条件:

  1. Each method is called only by one particular thread.

    每个办法会被一个非凡的线程所调用。

  2. Each method is called exactly once per State instance.

每个办法被每个State实例所调用

Note that the invocation order against other Actor methods is deliberately not specified.

留神Actor所执行的办法调用程序是成心不指定的。

Therefore, two or more Actor methods may be used to model the concurrent execution on data held by State instance.

因而,能够应用两个或多个Actor办法来模仿对State实例持有数据的并发执行。

Actor-annotated methods can have only the State or Result-annotated classes as the parameters.

只有被@Result或@State标记的类能力作为Actor办法(领有Actor注解)的参数

Actor methods may declare to throw the exceptions, but actually throwing the exception would fail the test.

Actor办法可能申明抛出异样,然而实际上抛出的异样会导致测试失败。

这里我来解读一下,II_Result 这个类上就带有@Result注解。当初咱们要解读的就是++v了,除了++v之外,咱们相熟的还有v++,如果当初问我这俩啥区别,我大概率答复的是++v 先执行自增,而后将自增后的值赋给v,对应的代码应该是像上面这样:

v = v + 1;

那v++呢,先应用值,后递增,我了解应该是像上面这样:

v = v; v = v + 1;

咱们日常应用比拟多的应该就是v++了:

for(int v = 0 ; v < 10 ; v++){}

for循环的括号是个小代码块,第一次执行的时候首先申明了一个v = 0,而后判断v是否小于10,如果小于执行v++,这个时候v的值依然是0,而后走循环体外面的内容。联合咱们下面的了解,这仿佛有些割裂,如果依照我对v++的了解,一条语句等价于两条语句,那么这两条语句还离开执行,这看起来十分怪。如果咱们将v++对一个变量进行赋值,而后这个变量拿到的就是自增之后的值,咱们也能够对咱们的了解打补丁,即在循环中两条语句不是程序执行,先执行v = v,循环体外面的内容执行结束再执行v = v + 1。那再欠缺一下呢,i++事实上被称为后缀表达式,语义为i+1,而后返回旧值。++i被称为前缀表达式, 语义为自增返回新值。乍一看如同是自增的,然而咱们在细化一下,在自增之前都要读取变量的值,所以无论是++v,还是v++,都须要先读值再自增,所以这是两个操作,在Java外面不是原子的,这是咱们的论断,咱们跑一下下面的例子,看下后果。

跑一下看一下后果

一般来说JCStress是要用命令行跑的,然而这对于老手不敌对,有些概念还须要了解一下,然而IDEA有专门为JCStress制作的插件,但这个插件并没有做到全版本的IDEA适配,目前适配的版本如下:

Aqua — 2023.1 (preview)

IntelliJ IDEA Educational — 2022.2 — 2022.2.2

IntelliJ IDEA Community — 2022.2 — 2023.1.2 (eap)

IntelliJ IDEA Ultimate — 2022.2 — 2023.1.2 (eap)

Android Studio — Flamingo | 2022.2.1 — Hedgehog | 2023.1.1 Canary 2

原本这个插件跑的是好好的,然而我换了台机器就发现如同有点问题,还是用命令行吧,首先咱们去GitHub将源码下载下来:

https://github.com/openjdk/jcstress.git

编译JCStress这个我的项目的master分支最好是JDK 17,我试了其余版本,发现都会有点奇奇怪怪的问题,咱们先不用深究这些问题,先跑进去后果再说. 首先咱们在命令行执行:

mvn clean install -DskipTests -T 1C

本篇的例子来自于JCStress tag中的0.3,然而跑JCStress测试的时候,发现0.3上如同有点水土不服,但提供的例子都是差不多,根本没变动。这里跑测试的时候,用的就是master分支上面的示例,咱们来跑下示例:

java -jar jcstress-samples/target/jcstress.jar -t API_01_Simple

跑完测试之后,测试报告会会呈现在results文件夹上面:

咱们关上看下测试报告:

运行的次数比咱们设想的要多,1877050次。

例子解读

API_02_Arbiters

Arbiter 意为冲裁。

/*    Another flavor of the same test as APISample_01_Simple is using arbiters.    另一个和APISample_01_Simple一样滋味的测试是应用仲裁者。        Arbiters run after both actors, and therefore can observe the final result.     Arbiters办法在actor办法运行之后执行,因而能够察看到最初的后果。    This allows to observe the permanent atomicity failure after both actors  finished.    在actor办法执行之后,Arbiters办法将会察看到原子性失败的后果        How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t API_02_Arbiters        ...      RESULT         SAMPLES     FREQ       EXPECT  DESCRIPTION           1     888,569,404    6.37%  Interesting  One update lost: atomicity failure.           2  13,057,720,260   93.63%   Acceptable  Actors updated independently. */@JCStressTest// These are the test outcomes.@Outcome(id = "1", expect = ACCEPTABLE_INTERESTING, desc = "One update lost: atomicity failure.")@Outcome(id = "2", expect = ACCEPTABLE,             desc = "Actors updated independently.")@Statepublic class API_02_Arbiters {    int v;    @Actor    public void actor1() {        v++;    }    @Actor    public void actor2() {        v++;    }    @Arbiter    public void arbiter(I_Result r) {        r.r1 = v;    }}

API_03_Termination

/*    Some concurrency tests are not following the "run continously" pattern.    一些并发测试可能并不实用"继续运行"模式。    One of interesting test groups is that asserts if the code had terminated after a signal.    一个乏味的测试是确定代码是否在接管到信号之后终止    Here, we use a single @Actor that busy-waits on a field, and a @Signal that    sets that field.    在上面的例子中,咱们应用@Actor来让一个线程在一个字段上忙等,而后用@Signal办法去设置这个字段。    JCStress would start actor, and then deliver the signal.    JCStress会首先启动actor,而后启动signal.    If it exits in reasonable time, it will record "TERMINATED" result, otherwise    record "STALE".    如果在肯定的工夫退出,它将会记录"TERMINATED",否则将会记录state。    How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t API_03_Termination        ...          RESULT  SAMPLES     FREQ       EXPECT  DESCRIPTION           STALE        1   <0.01%  Interesting  Test hung up.      TERMINATED   13,168   99.99%   Acceptable  Gracefully finished.      Messages:        Have stale threads, forcing VM to exit for proper cleanup. */@JCStressTest(Mode.Termination)@Outcome(id = "TERMINATED", expect = ACCEPTABLE,             desc = "Gracefully finished.")@Outcome(id = "STALE",      expect = ACCEPTABLE_INTERESTING, desc = "Test hung up.")@Statepublic class API_03_Termination {    @Actor    public void actor1() throws InterruptedException {        synchronized (this) {            wait();        }    }    @Signal    public void signal() {        synchronized (this) {            notify();        }    }}

这里咱们不意识的也就是 @Signal,咱们看下这个类上的正文:

/** * {@link Signal} is useful for delivering a termination signal to {@link Actor}  in {@link Mode#Termination} tests. * Signal 被用于 在中断测试中发送中断信号给Actor。 *  It will run after {@link Actor} in question  started executing. *  它将会在相应的Actor运行之后开始执行。 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Signal {}

API_04_Nesting

/*It is sometimes convenient to put the tests in the same source file for better comparison. JCStress allows to nest tests like this:有时候将一些测试放入一个源文件能够更好的比拟,JCStress容许将上面一样嵌套    How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t API_04_Nesting        ...        .......... [OK] org.openjdk.jcstress.samples.api.APISample_04_Nesting.PlainTest          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION            1, 1   21,965,585    4.5%  Interesting  Both actors came up with the same value: atomicity failure.            1, 2  229,978,309   47.5%   Acceptable  actor1 incremented, then actor2.            2, 1  232,647,044   48.0%   Acceptable  actor2 incremented, then actor1.        .......... [OK] org.openjdk.jcstress.samples.api.APISample_04_Nesting.VolatileTest          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION            1, 1    4,612,990    1.4%  Interesting  Both actors came up with the same value: atomicity failure.            1, 2   95,520,678   28.4%   Acceptable  actor1 incremented, then actor2.            2, 1  236,498,350   70.3%   Acceptable  actor2 incremented, then actor1. */public class API_04_Nesting {    @JCStressTest    @Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")    @Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")    @Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")    @State    public static class PlainTest {        int v;        @Actor        public void actor1(II_Result r) {            r.r1 = ++v;        }        @Actor        public void actor2(II_Result r) {            r.r2 = ++v;        }    }    @JCStressTest    @Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")    @Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")    @Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")    @State    public static class VolatileTest {        volatile int v;        @Actor        public void actor1(II_Result r) {            r.r1 = ++v;        }        @Actor        public void actor2(II_Result r) {            r.r2 = ++v;        }    }}

API_05_SharedMetadata

/*    In many cases, tests share the outcomes and other metadata. To common them,    there is a special @JCStressMeta annotation that says to look up the metadata    at another class.    在一些状况下,测试须要共享输入后果和其余元数据,为了共享他们,有一个非凡的注解@JCStressMeta来批示其余类在另一个类外面查找元数据。    How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t API_05_SharedMetadata        ...        .......... [OK] org.openjdk.jcstress.samples.api.APISample_05_SharedMetadata.PlainTest          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION            1, 1    6,549,293    1.4%  Interesting  Both actors came up with the same value: atomicity failure.            1, 2  414,490,076   90.0%   Acceptable  actor1 incremented, then actor2.            2, 1   39,540,969    8.6%   Acceptable  actor2 incremented, then actor1.        .......... [OK] org.openjdk.jcstress.samples.api.APISample_05_SharedMetadata.VolatileTest          RESULT      SAMPLES    FREQ       EXPECT  DESCRIPTION            1, 1   15,718,942    6.1%  Interesting  Both actors came up with the same value: atomicity failure.            1, 2  120,855,601   47.2%   Acceptable  actor1 incremented, then actor2.            2, 1  119,393,635   46.6%   Acceptable  actor2 incremented, then actor1. */@Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")@Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")@Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")public class API_05_SharedMetadata {    @JCStressTest    @JCStressMeta(API_05_SharedMetadata.class)    @State    public static class PlainTest {        int v;        @Actor        public void actor1(II_Result r) {            r.r1 = ++v;        }        @Actor        public void actor2(II_Result r) {            r.r2 = ++v;        }    }    @JCStressTest    @JCStressMeta(API_05_SharedMetadata.class)    @State    public static class VolatileTest {        volatile int v;        @Actor        public void actor1(II_Result r) {            r.r1 = ++v;        }        @Actor        public void actor2(II_Result r) {            r.r2 = ++v;        }    }}

咱们首先来看一下 @JCStressMeta这个注解:

/** * {@link JCStressMeta} points to another class with test meta-information. *    @JCStressMeta注解将指向另一个蕴含测试元信息的类。 * <p>When placed over {@link JCStressTest} class, the {@link Description}, {@link Outcome}, * {@link Ref}, and other annotations will be inherited from the pointed class. This allows * to declare the description, grading and references only once for a group of tests.</p> *  JCStressMeta  * 当它和@JCStressTest一起呈现,@Description、@Outcome、@Ref等其余注解将会从指向的类继承。这样就能够在一组测试中只申明一次描* 述、评分和参考信息 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface JCStressMeta {    Class value();}

API_06_Descriptions

/*    JCStress also allows to put the descriptions and references right at the test.    This helps to identify the goal for the test, as well as the discussions about    the behavior in question.    JCStress 还容许将援用和形容放入测试中,这将帮咱们认清测试指标, 以及相干问题的探讨    How to run this test:       $ java -jar jcstress-samples/target/jcstress.jar -t API_06_Descriptions */@JCStressTest// Optional test description@Description("Sample Hello World test")// Optional references. @Ref is repeatable.@Ref("http://openjdk.java.net/projects/code-tools/jcstress/")@Outcome(id = "1, 1", expect = ACCEPTABLE_INTERESTING, desc = "Both actors came up with the same value: atomicity failure.")@Outcome(id = "1, 2", expect = ACCEPTABLE,             desc = "actor1 incremented, then actor2.")@Outcome(id = "2, 1", expect = ACCEPTABLE,             desc = "actor2 incremented, then actor1.")@Statepublic class API_06_Descriptions {    int v;    @Actor    public void actor1(II_Result r) {        r.r1 = ++v;    }    @Actor    public void actor2(II_Result r) {        r.r2 = ++v;    }}

总结一下

本篇咱们介绍的JCStress注解有:

  • @JCStressTest 标定这个类须要被JCStress测试
  • 输出参数 II_Result 用于记录测试后果 ,这个类型的后果几个I,就是由几个变量,记录的后果最初会被用逗号拼接,而后和outcome注解的id来匹配。
  • Outcome 的id属性用于设置咱们心愿测进去的后果,expect 用于标定测试等级。
  • @Actor 标定的办法 会被一个线程执行,只有类上呈现了@State,类上的办法才能够容许呈现@Actor注解。
  • @Arbiter 在actor办法之后执行
  • @Signal 被用于在中断测试中发送中断信号给Actor。
  • 你也能够将一些测试放在一个类中
  • 有的时候 你想做的并发测试,预期输入后果都一样,咱们能够用@JCStressMeta注解,共享属性。
  • 有的时候咱们也想和他人探讨并发问题,并心愿在测试输入的文档中附带参考链接和形容,咱们能够用@Ref和@Description。

写在最初

这个框架倒是我头一次只看官网文档,依据例子写的教程,摸索的过程倒是摔了不少跟头,刚开始不想用命令行,想用IDEA的一个插件JCStress来跑,换了一台电脑之后,发现这个插件有点水土不服,原本想看下问题出在哪里,然而想想要花不少的工夫,这个想法也就打消了。其实本篇还打算介绍一下线程中断机制,然而感觉这个内容一篇之内也介绍不完,所性也砍了,原本想解读测试报告的时候,把JCStress的执行机制也顺带介绍一下,Java的两个编译器C1、C2。然而刚敲几个字就发现也是不小的篇幅,所性也砍了。前面咱们介绍JMM,将用JCStress来验证咱们的猜测。

参考资料

[1] Difference between pre-increment and post-increment in a loop? https://stackoverflow.com/questions/484462/difference-between-pre-increment-and-post-increment-in-a-loop

[2] Why is i++ not atomic? https://stackoverflow.com/questions/25168062/why-is-i-not-atomic