对数组和数组元素的 AtomicReference 更改可见性

AtomicReference to array and array element changes visibility

Java 是否保证 线程 A 在将数组引用存储在 AtomicReference 之前对数组元素所做的更新将始终对 [= 可见30=]获得这个引用的线程B?

换句话说,执行此操作的可能输出是什么:

class References {
    AtomicReference<String[]> refs = new AtomicReference<>(new String[]{"first"});

    public void add(String s) {
        refs.updateAndGet(oldRefs -> {
            String[] newRefs = new String[oldRefs.length + 1];
            System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length);
            newRefs[oldRefs.length] = s;
            return newRefs;
        });
    }

    public static void main(String[] args) {
        References r = new References();
        new Thread(() -> r.add("second")).start();
        System.out.println(Arrays.toString(r.refs.get()));
    }
}

以上可以只打印 [first][first, second],还是也可以得到像 [first, null][null, null] 这样的结果?

java.util.concurrent.atomic 的 javadoc 指出:

compareAndSet and all other read-and-update operations such as getAndIncrement have the memory effects of both reading and writing volatile variables.

这似乎对数组的非易失性元素没有任何保证,只有数组引用本身。

您无法在数组中获取任何空元素,因为您没有修改代码中的数组元素,而是每次都创建一个新元素 (String[] newRefs = new String[oldRefs.length + 1];)。由于您正在存储数组引用并且您没有修改其元素,因此您看不到空元素。 如果在你的代码中你有类似的东西:

   refs.updateAndGet(oldRefs -> {
        if (oldRefs.size>0) oldRefs[0]=null;//is an example just for "fun"
        String[] newRefs = new String[oldRefs.length + 1];
        System.arraycopy(oldRefs, 0, newRefs, 0, oldRefs.length);
        newRefs[oldRefs.length] = s;
        return newRefs;
    });

然后你会看到一些不同的东西,有些用户可能会在第一个元素中看到空值

更新: 由于 AtomicReference 仅适用于对数组的引用,您可以使用 AtomicReferenceArray 以更安全的方式访问数组元素

数组的 AtomicReference 与其他数组没有什么不同 - 只有 reference 是原子的,因此具有相关的内存屏障。访问数组就像任何其他对象一样 - 没有额外的保护。

因此,您将始终获得 [first][first, second] 而没有其他选项,因为您正在创建新数组、填充它并将其放回受原子保护的旧引用。

Java 数组不太擅长调整大小。如果你想要一个可调整大小的结构,你最好使用 ArrayList。如果您想并发访问它,请使用 CopyOnWriteArrayList,这实际上就是您要在代码中实现的内容。

对变量的易失性写入将保证在它之前发生的所有事情都将在后续对同一变量易失性读取之前发生。

来自 JLS 的相关规则是:

17.4.4. Synchronization Order

  • A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

17.4.5. Happens-before Order

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.

  • If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
  • If an action x synchronizes-with a following action y, then we also have hb(x, y).

  • If hb(x, y) and hb(y, z), then hb(x, z).

由于 AtomicReference 保证您的数组引用只是 stored/loaded 以易变的方式(并且一旦写入,您就不会修改现有数组),这足以保证可见性任何调用 refs.get().

的人的 System.arrayCopy()(及其后一行)的结果

然而,该构造仍然不是完全无懈可击的,因为任何通过 refs.get() 获得数组引用的人都可以在没有保护的情况下继续对元素进行更改AtomicReference.

CopyOnWriteArrayList 与此非常相似(它使用 ReentrantLockvolatile 数组字段的组合而不是 AtomicReference),除了它也是保证没有人可以获取底层数组并以不安全的方式使用它。