共计 16188 个字符,预计需要花费 41 分钟才能阅读完成。
前言
咱们前文提到,当咱们对实践有一些理解,咱们就渴望验证,如果无奈对实践进行验证,那么咱们就可能对实践将信将疑,那对于 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
@State
public 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 是解决测试状态的核心注解,有这个注解的类,在测试中负责读取和批改数据。这些类要求以下两个属性
State class should be public, non-inner class.
State 类该当是 public,而不是外部类
- 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 的其余办法和成员变量
@Result
public class II_Result implements Serializable {public String toString() {return ""+ r1 +", " + r2;}
}
// 省略 III_Result 的其余办法和成员变量
@Result
public 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 是外围测试注解,被它标记的办法将会一个线程所执行,基础架构会保护一组不变的条件:
Each method is called only by one particular thread.
每个办法会被一个非凡的线程所调用。
- 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.")
@State
public 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.")
@State
public 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.")
@State
public 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