下面是高并发测试框架jcstress下写的一段非常简单代码:
package com.dsf.jcstress;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.II_Result;
import org.openjdk.jcstress.infra.results.IZ_Result;
import org.openjdk.jcstress.infra.results.I_Result;
@JCStressTest
@Outcome(id = {"0, 0", "1, 1", "1, 0"}, expect = Expect.ACCEPTABLE)
@Outcome(id = {"0, 1"}, expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class ConcurrencyTest2 {
int num1 = 0;
volatile int num2 = 0;
@Actor
public void actor1(II_Result r) {
r.r1 = num1;
r.r2 = num2;
}
@Actor
public void actor2(II_Result r) {
num1 = 1;
num2 = 1;
}
}
再上结果:
*** INTERESTING tests
Some interesting behaviors observed. This is for the plain curiosity.
4 matching test results.
[OK] com.dsf.jcstress.ConcurrencyTest2
(JVM args: [-XX:-TieredCompilation])
Observed state Occurrences Expectation Interpretation
0, 0 48,919,887 ACCEPTABLE
0, 1 18,521 ACCEPTABLE_INTERESTING
1, 0 32,419 ACCEPTABLE
1, 1 22,147,984 ACCEPTABLE
[OK] com.dsf.jcstress.ConcurrencyTest2
(JVM args: [-XX:TieredStopAtLevel=1])
Observed state Occurrences Expectation Interpretation
0, 0 52,070,740 ACCEPTABLE
0, 1 50,050 ACCEPTABLE_INTERESTING
1, 0 1,300 ACCEPTABLE
1, 1 21,298,781 ACCEPTABLE
[OK] com.dsf.jcstress.ConcurrencyTest2
(JVM args: [-Xint])
Observed state Occurrences Expectation Interpretation
0, 0 3,402,062 ACCEPTABLE
0, 1 64,091 ACCEPTABLE_INTERESTING
1, 0 3,037 ACCEPTABLE
1, 1 2,285,171 ACCEPTABLE
[OK] com.dsf.jcstress.ConcurrencyTest2
(JVM args: [])
Observed state Occurrences Expectation Interpretation
0, 0 59,145,816 ACCEPTABLE
0, 1 26,043 ACCEPTABLE_INTERESTING
1, 0 40,703 ACCEPTABLE
1, 1 16,144,649 ACCEPTABLE
结果分析:
我们都知道给共享变量加上volatile会为该变量的写指令加上写屏障,从而阻止指令前后的重排序,那么,为什么这里给num2加了volatile,仍然出现num1=0,num2=1这种情况呢?
首先volatile禁用指令重排序在jdk1.8中是毋庸置疑的,问题出现在actor1这里,之所以会出现“0, 1”这种结果是因为actor1先获取了num1=0,然后切换到actor2线程,执行完num1=1,num2=1,然后由于volatile的可见性,actor1获知了num2=1,从而出现了看似写屏障失效的现象,将actor1稍作改动即可复现我们预知的结果:
@Actor
public void actor1(II_Result r) {
r.r2 = num2; //这里一定要先获取num2
r.r1 = num1;
}