在这种情况下,是否会抛出此AssertionError?

时间:2018-01-07 14:26:40

标签: java multithreading concurrency visibility safe-publication

首先关闭代码,来自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的线程,因为之前发生的保证,所有后续线程都可以安全地成为完全可见的持有者。我是对的吗?

我甚至尝试运行此代码很长时间而且没有断言错误。

但是,如果主要看起来像下面那样,那么我相信它可能会出现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();
        }
    }

1 个答案:

答案 0 :(得分:1)

是的,这是安全的,因为Thread#start保证之前发生。更加罗嗦:对Thread#start之前发生的任何变量的任何读/写(如果你愿意的话,我倾向于按照程序顺序认为 )也会在之前发生 em>该线程中的任何操作(它是run方法)。

实际上,如果以前没有发生过(允许重新排序)并且程序执行允许那些潜在的重新排序,那么可能会发生破坏并抛出该错误。我甚至倾向于说一个适用于弱内存模型的CPU(假设你使用英特尔,这是一个强大的内存模型)可能会增加这个机会,但我不确定。

因此,据我所知,这些操作将按以下顺序进行:首先,使用变量n重新排序发布引用(之前没有发生过,所以这是允许)。 Thread1创建Holder的实例。 Thread2看到发布的引用并调用该方法。它将变量n读为zero(请记住,重新排序已经发生并且n尚未写入,因此默认值为zero),因此它是!=检查,创建了Holder的Thread1,在{/ em> Thread2之前将n写为12,例如再次读取它(在!=n部分中)。所以这可能会失败。

使值final可以解决这个问题,因为它会引入正确的内存障碍,或规则之前发生。