如何在实践中理解本书 java 并发的示例?

How to understand an example of book java concurrency in practice?

Listing 3.15. Class at Risk of Failure if Not Properly Published.

public class Holder {
 private int n;
 public Holder(int n) { this.n = n; }
 public void assertSanity() {
 if (n != n)
 throw new AssertionError("This statement is false.");
 }
} 

我的第一个问题是javac为什么不优化if (n != n)

下面是我的例子demo

public class TestSync {
    private int n;

    public TestSync(int n) {
        this.n = n;
    }

    public void assertSanity() {
        if(n!=n)
            throw new AssertionError("This statement is false");
    }

    private static TestSync test;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    if(test == null) test = new TestSync(2);
                    else test = null;
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    if(test != null)
                        try {
                            test.assertSanity();
                        } catch (NullPointerException e) {

                        }
                }
            }
        }).start();
    }
}

我的第二个问题是我做对了吗?因为我运行 demo的时候没有出现异常。

更新

1.Addition 我的第一个问题: javap -c TestSync.class

public void assertSanity();
    Code:
       0: aload_0
       1: getfield      #3                  // Field n:I
       4: aload_0
       5: getfield      #3                  // Field n:I
       8: if_icmpeq     21
      11: new           #4                  // class java/lang/AssertionError
      14: dup
      15: ldc           #5                  // String This statement is false
      17: invokespecial #6                  // Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
      20: athrow
      21: return

我认为 javac 会将 if(n!=n) 优化为 if(false) 并缩小它。

2.Why 我还是在if(test != null)?

后面加上try{}catch(NullPointerException e)

因为我认为字段 test 可能会在 if(test!=null) 之后被另一个线程设置 null

首先,javac 几乎从不优化代码,你在编译。只有当值是 编译时常量 时,才需要 javac 来在编译时计算表达式,它本身形成编译时常量,请参阅 JLS §15.28. Constant Expressions

然而,操作在运行时得到优化,甚至没有线程同步措施也允许优化器使用乐观的假设,比如变量在两次读取之间不会改变。因此,n!=n 表达式以很低的可能性开始计算为 true,因为读取之间的时间很短¹,并且在优化器启动后几乎永远不会是 true。所以虽然表达式n!=n 不能保证总是 false,在实践中不太可能遇到 true

当然,根据墨菲定律,无论如何,当您尝试引发该错误时永远不会发生这种情况,但偶尔会在客户那里发生,但永远不会重现...

¹ 请注意,即使第二个线程由于竞争条件读取初始 0 值,n!=n 也只会失败,如果没有再次读取初始 0后续阅读。