下面是高并发测试框架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; }