空引用的安全初始化

Safe initialisation of null reference

我想知道初始化为 null 的非最终字段(如果有)存在哪些发布保证。

考虑以下片段:

public class MyClass {

    private final CopyOnWriteArrayList<Inner> list = new CopyOnWriteArrayList<>();

    //called by thread 1
    public void init() {
        // initialise Inner instance
        list.add(new Inner());
    }

    //called by thread 2
    public void doSomething() {
        for (Inner i : list) {
            // access non-final field
            Object ref = i.ref;
            // do something
            // ...
            // ...

            // potentially set i.ref
        }
    }

    private static class Inner {
        // initialised by thread 1
        Object ref = null;
    }
}

假设doSomething()总是被线程2调用,这样安全吗?线程 2 在第一次访问时会看到什么保证?线程 2 是否有可能看到非空的东西?

JMM 中哪里描述了这种情况的语义?

仅当您使用 具有状态 的有意义的对象初始化字段时,谈论安全发布才有意义。然后,不正确的发布可能导致观察到一个部分构造的对象

在这种情况下,null 不传达任何状态。它可以被认为是一个不可变的对象。不可变对象没有发布问题。

What guarantees are made about what thread 2 will see the first time it's accessed?

线程 2 在引用 i.ref 时将看到 null
请注意,该列表可能为空,因为线程 1 可能尚未向其添加 Inner

Is there any possibility thread 2 would see something that's non-null?

没有

JVM 将保证您不会看到 凭空值 ,所以除 null 之外的任何值都不会可能 ,以防 List 不为空(当然在本例中)。如果涉及不同的线程(假设 Thread3)会更改您的列表(向其添加元素),则 Thread2 可以看到这些更新。请注意 CopyOnWriteArrayList 的各个方法是线程安全的;你的方法 doSomething 不是

参见JLS for the specifics or the excellent (and rather complicated, may be just to me) Aleksey article