在这种情况下永远不会抛出这个 AssertionError 吗?

Will this AssertionError never be thrown in this case?

首先是代码,来自 JCIP 列表 http://jcip.net/listings/StuffIntoPublic.javahttp://jcip.net/listings/Holder.java

public class SafePublication {
    public static void main(String[] args) throws InterruptedException {
//        System.out.println(Thread.currentThread().getName());
        StuffIntoPublic t = new StuffIntoPublic();
        t.initialize();
        while (true) {
            new Thread(() -> { t.holder.assertSanity(); }).start();
        }
    }
}

//@author Brian Goetz and Tim Peierls
class StuffIntoPublic {
    public Holder holder;

    public void initialize() {
//        System.out.println(Thread.currentThread().getName());
        holder = new Holder(42);
    }
}

//@author Brian Goetz and Tim Peierls
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.");
        }
    }
}

我是说在这种情况下永远不会抛出 AssertionError,因为 Thread.start() 发生在保证之前。注释的 System.out.printlns 都打印 main,这意味着主线程是通过在 while(true) 循环中的线程上创建和调用 start 来生成所有后续线程的。

并且由于这是创建和初始化 Holder 的线程,因此由于发生前保证,所有后续线程都可以安全地成为完全可见的持有者。我说得对吗?

我什至尝试了 运行 这段代码很长时间,没有断言错误。

但是,如果 main 如下所示,那么我相信有可能出现 AssertionError

 public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        StuffIntoPublic t = new StuffIntoPublic();
        new Thread(() -> t.initialize() ).start();
        while (true) {
            new Thread(() -> { t.holder.assertSanity(); }).start();
        }
    }

是的,这是安全的,因为 Thread#start 保证 happens-before。更罗嗦:任何 read/write 发生在 Thread#start 之前的任何变量(如果你愿意,我倾向于认为程序顺序 高于 ),也会发生 该线程中的任何操作之前(它是run方法)。

确实,如果之前没有发生(允许重新排序)并且程序执行允许这些潜在的重新排序,那可能会中断并抛出该错误。我什至倾向于说 适当的 CPU 与弱内存模型一起使用(假设您使用的是英特尔,这是一种强内存模型)可能会增加这种机会,但我我不确定。

因此,据我所知,操作将按以下顺序进行:首先,发布引用是用变量 n 重新排序的(没有 happens-before,所以这是允许的)。 Thread1 创建了 Holder 的一个实例。 Thread2 看到已发布的引用并调用该方法。它读取变量 nzero(记住重新排序发生并且 n 尚未写入,因此具有默认值 zero),因此它然后!= 检查 但是 创建 Holder 的线程 1,将 n 写入 12 例如 之前 Thread2 再次读取它(在 !=n 部分)。所以这可能会失败。

设置值 final 可以解决这个问题,因为它引入了正确的内存屏障,或者 happens-before 规则。