当 Eclipse 编译其值在 switch
语句中使用的枚举时,它会在枚举类文件中添加以下内容:
- 合成字段:
private static int[] $SWITCH_TABLE$mypackage$MyEnum;
- 合成方法:
static int[] $SWITCH_TABLE$mypackage$MyEnum()
使用相应的枚举序数值延迟初始化$SWITCH_TABLE$mypackage$MyEnum
字段。
在枚举值发生切换的每个地方,它都会调用 $SWITCH_TABLE$mypackage$MyEnum()[e.ordinal()]
。
源代码:
package mypackage;
public enum MyEnum {
FIRST;
public int get() {
switch (this) {
case FIRST:
return 2;
default:
return -1;
}
}
}
反编译代码:
package mypackage;
public enum MyEnum {
FIRST;
// $FF: synthetic field
private static int[] $SWITCH_TABLE$mypackage$MyEnum;
public int get() {
switch($SWITCH_TABLE$mypackage$MyEnum()[this.ordinal()]) {
case 1:
return 2;
default:
return -1;
}
}
// $FF: synthetic method
static int[] $SWITCH_TABLE$mypackage$MyEnum() {
int[] var10000 = $SWITCH_TABLE$mypackage$MyEnum;
if (var10000 != null) {
return var10000;
} else {
int[] var0 = new int[values().length];
try {
var0[FIRST.ordinal()] = 1;
} catch (NoSuchFieldError var1) {
}
$SWITCH_TABLE$mypackage$MyEnum = var0;
return var0;
}
}
}
但是,正如您在这里看到的,$SWITCH_TABLE$mypackage$MyEnum
字段既不是volatile
也不是$SWITCH_TABLE$mypackage$MyEnum()
正在执行任何同步。
现在的问题是:这实际上是线程安全的吗?如果我正确理解 Java 内存模型,则不能保证如果一个线程初始化开关表(通过调用 get()
),第二个线程会看到最新的开关表值。相反,它可能会看到一个没有正确元素的陈旧值。
后续问题:由于没有happens-before 关系,是否也允许 JVM 在元素更改之前重新排序对 $SWITCH_TABLE$
的分配?如果这是可能的,则可能会出现竞争条件,即一个线程看到数组而另一个线程仍在修改元素。
最佳答案
我在 Windows 7/OpenJDK 13 上用 jcstress 运行了一些测试。局部变量很重要!
此代码应模仿已发布的代码,多次运行 3 个线程:
// concurrency stress test
@JCStressTest
// expected outcome (III_Result::toString)
@Outcome(id = "1, 1, 1", expect = Expect.ACCEPTABLE, desc = "Correctly initialized.")
@Outcome(id = ".+, .+, .+", expect = Expect.FORBIDDEN, desc = "Some Thread did see a not initialized array")
// test data
@State
public class EnumInitOK {
int[] array;
int[] switchTable() {
int[] v1 = array;
if (v1 != null) {
return v1;
} else {
int[] v2 = new int[1];
v2[0] = 1;
array = v2;
return v2;
}
}
// each called only by one particular Thread, once per State instance
@Actor public void test1(III_Result r) {
r.r1 = switchTable()[0];
}
@Actor public void test2(III_Result r) {
r.r2 = switchTable()[0];
}
@Actor public void test3(III_Result r) {
r.r3 = switchTable()[0];
}
}
所有使用不同设置运行的结果,没有一个初始化错误(将近 400M 测试):
Java Concurrency Stress Tests --------------------------------------------------------------------------------- Rev: null, built by cfh with 12.0.2 at null Probing what VM modes are available: (failures are non-fatal, but may miss some interesting cases) ----- [OK] [-Xint] ----- [OK] [-XX:TieredStopAtLevel=1] ----- [OK] [] ----- [OK] [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM] ----- [OK] [-XX:-TieredCompilation] ----- [OK] [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM] Initializing and probing the target VM: (all failures are non-fatal, but may affect testing accuracy) ----- [OK] Unlocking diagnostic VM options Burning up to figure out the exact CPU count....... done! ----- [OK] Trimming down the default VM heap size to 1/4-th of max RAM ----- [OK] Trimming down the number of compiler threads ----- [OK] Trimming down the number of parallel GC threads ----- [OK] Trimming down the number of concurrent GC threads ----- [OK] Trimming down the number of G1 concurrent refinement GC threads ----- [OK] Testing @Contended works on all results and infra objects ----- [OK] Unlocking Whitebox API for online de-optimization ----- [OK] Testing allocation profiling ----- [OK] Trying Thread.onSpinWait Hardware threads in use/available: 4/4, using Thread.onSpinWait() Test preset mode: "default" Writing the test results to "jcstress-results-2019-10-22-23-32-40.bin.gz" Parsing results to "results/" Running each test matching "cfh.jcstress.EnumInitOK" for 1 forks, 5 iterations, 1000 ms each Each JVM would execute at most 5 tests in the row. Solo stride size will be autobalanced within [10, 10000] elements, but taking no more than 100 Mb. ... [output deleted] ... RUN COMPLETE. RUN RESULTS: ------------------------------------------------------------------------------------------------------------------------ *** INTERESTING tests Some interesting behaviors observed. This is for the plain curiosity. 0 matching test results. *** FAILED tests Strong asserts were violated. Correct implementations should have no assert failures here. 0 matching test results. *** ERROR tests Tests break for some reason, other than failing the assert. Correct implementations should have none. 0 matching test results. *** All remaining tests Tests that do not fall into any of the previous categories. 6 matching test results. [OK] cfh.jcstress.EnumInitOK (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 76.194.931 ACCEPTABLE Correctly initialized. [OK] cfh.jcstress.EnumInitOK (JVM args: [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 76.756.011 ACCEPTABLE Correctly initialized. [OK] cfh.jcstress.EnumInitOK (JVM args: [-XX:-TieredCompilation]) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 78.453.381 ACCEPTABLE Correctly initialized. [OK] cfh.jcstress.EnumInitOK (JVM args: [-XX:TieredStopAtLevel=1]) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 73.608.821 ACCEPTABLE Correctly initialized. [OK] cfh.jcstress.EnumInitOK (JVM args: [-Xint]) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 8.740.641 ACCEPTABLE Correctly initialized. [OK] cfh.jcstress.EnumInitOK (JVM args: []) Observed state Occurrences Expectation Interpretation .+, .+, .+ 0 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 86.069.111 ACCEPTABLE Correctly initialized. ------------------------------------------------------------------------------------------------------------------------
为了交叉检查,我在 switchTable
方法中尝试了直接访问 array
的相同代码,而不使用局部变量:
//concurrency stress test
@JCStressTest
//expected outcome (III_Result::toString)
@Outcome(id = "1, 1, 1", expect = Expect.ACCEPTABLE, desc = "Correctly initialized.")
@Outcome(id = ".+, .+, .+", expect = Expect.FORBIDDEN, desc = "Some Thread did see a not initialized array")
//test data
@State
public class EnumInitErr {
int[] array;
int[] switchTable() {
if (array != null) {
return array;
} else {
array = new int[1];
array[0] = 1;
return array;
}
}
// each called only by one particular Thread, once per State instance
@Actor public void test1(III_Result r) {
r.r1 = switchTable()[0];
}
@Actor public void test2(III_Result r) {
r.r2 = switchTable()[0];
}
@Actor public void test3(III_Result r) {
r.r3 = switchTable()[0];
}
}
这次我们得到了一些未初始化数组的结果:
... [output deleted] ... RUN COMPLETE. RUN RESULTS: ------------------------------------------------------------------------------------------------------------------------ *** INTERESTING tests Some interesting behaviors observed. This is for the plain curiosity. 0 matching test results. *** FAILED tests Strong asserts were violated. Correct implementations should have no assert failures here. 6 matching test results. [FAILED] cfh.jcstress.EnumInitErr (JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]) Observed state Occurrences Expectation Interpretation 0, 0, 1 565 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 1.089 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 2.835 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 792 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 3.272 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 2.394 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 70.441.694 ACCEPTABLE Correctly initialized. [FAILED] cfh.jcstress.EnumInitErr (JVM args: [-XX:-TieredCompilation, -XX:+UnlockDiagnosticVMOptions, -XX:+StressLCM, -XX:+StressGCM]) Observed state Occurrences Expectation Interpretation 0, 0, 1 481 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 604 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 2.373 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 901 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 2.908 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 3.310 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 69.456.024 ACCEPTABLE Correctly initialized. [FAILED] cfh.jcstress.EnumInitErr (JVM args: [-XX:-TieredCompilation]) Observed state Occurrences Expectation Interpretation 0, 0, 1 1.332 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 948 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 4.212 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 1.071 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 5.216 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 5.052 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 64.580.410 ACCEPTABLE Correctly initialized. [FAILED] cfh.jcstress.EnumInitErr (JVM args: [-XX:TieredStopAtLevel=1]) Observed state Occurrences Expectation Interpretation 0, 0, 1 8.766 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 11.470 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 21.761 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 11.280 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 28.461 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 27.634 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 71.079.569 ACCEPTABLE Correctly initialized. [FAILED] cfh.jcstress.EnumInitErr (JVM args: [-Xint]) Observed state Occurrences Expectation Interpretation 0, 0, 1 77 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 117 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 2.417 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 83 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 2.291 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 1.789 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 5.674.177 ACCEPTABLE Correctly initialized. [FAILED] cfh.jcstress.EnumInitErr (JVM args: []) Observed state Occurrences Expectation Interpretation 0, 0, 1 785 FORBIDDEN Some Thread did see a not initialized array 0, 1, 0 804 FORBIDDEN Some Thread did see a not initialized array 0, 1, 1 2.717 FORBIDDEN Some Thread did see a not initialized array 1, 0, 0 1.250 FORBIDDEN Some Thread did see a not initialized array 1, 0, 1 4.041 FORBIDDEN Some Thread did see a not initialized array 1, 1, 0 5.500 FORBIDDEN Some Thread did see a not initialized array 1, 1, 1 63.510.464 ACCEPTABLE Correctly initialized. *** ERROR tests Tests break for some reason, other than failing the assert. Correct implementations should have none. 0 matching test results. *** All remaining tests Tests that do not fall into any of the previous categories. 0 matching test results. ------------------------------------------------------------------------------------------------------------------------
第一次使用jcstress
,如有错误请见谅
关于java - Eclipse 的 $SWITCH_TABLE$ 是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58507978/