java - Eclipse 的 $SWITCH_TABLE$ 是线程安全的吗?

标签 java eclipse java-memory-model

当 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/

相关文章:

Java:相当于C的strnicmp? (包括 startsWith 和 ignoreCase)

java - 自己的偏好页面 : Enable and disable FieldEditor by BooleanFieldEditor

java - fork/join,输入数组是否需要同步?

java - 为什么如果我们重写 Finalize 方法可以增加分配阶段?

java - 用于并发访问的 Jackrabbit 存储库锁定

java - 调用方法的值被修改

eclipse - 为什么Eclipse找不到原语和容器的源代码?

Java——关闭扫描器和资源泄漏

java - Java中同步的内存效应

java - 如何在不使用构造函数的情况下初始化 AspectJ 切面?