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

Posted

技术标签:

【中文标题】在这种情况下永远不会抛出这个 AssertionError 吗?【英文标题】:Will this AssertionError never be thrown in this case? 【发布时间】:2018-06-16 17:05:54 【问题描述】:

首先是代码,来自 JCIP 列表 http://jcip.net/listings/StuffIntoPublic.java 和 http://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 的线程,因此所有后续线程都可以安全地成为一个完全可见的 Holder,因为它具有happens-before 保证。我说的对吗?

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

但是,如果 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();
        
    

【问题讨论】:

你从哪里得到SafePublication类代码? 我写了那个代码 那我不明白你的问题 实际上,SafePublication 并不安全,因为holder = new Holder(42); 是不安全的发布。 SafePublication的main方法中可以抛出AssertionError吗? 【参考方案1】:

是的,这是安全的,因为 Thread#start 保证 happens-before。说得更啰嗦:在Thread#start 之前发生的任何变量的任何读/写操作(如果你愿意,我倾向于按程序顺序高于 考虑),也将发生在之前该线程中的任何操作(它是run 方法)。

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

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

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

【讨论】:

以上是关于在这种情况下永远不会抛出这个 AssertionError 吗?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 中的 Error Handling

为啥在这种情况下允许抛出已检查的异常类型?

为啥即使永远不会抛出 IOException 也可以在 java 7 中捕获 IOException

FusedLocationClient.getLastLocation(),永远不会到达 onSuccess

对象扩展语法永远不会抛出错误吗?

为啥在这种情况下编译器会抛出“未定义的引用...”错误?