对阵列和数组元素的AtomicReference改变了可见性

时间:2015-07-03 08:56:12

标签: java arrays multithreading atomic

Java是否保证在AtomicReference中存储数组引用之前对线程A 完成的数组元素的更新将始终对线程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以及所有其他读取和更新操作(例如getAndIncrement)都会影响读取和写入volatile变量。

这似乎不能保证数组的非易失性元素,只有数组引用本身。

3 个答案:

答案 0 :(得分:5)

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

JLS的相关规则是:

  

<强> 17.4.4。同步订单

     
      
  • 对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中“后续”根据同步顺序定义)。
  •   
     

<强> 17.4.5。发生在订单之前

     

可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并且在第二个之前被命令。

     

如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前。

     
      
  • 如果x和y是同一个线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)。
  •   
  • 如果动作x与后续动作y同步,那么我们也有hb(x,y)。

  •   
  • 如果是hb(x,y)和hb(y,z),那么hb(x,z)。

  •   

由于AtomicReference保证您的数组引用仅以易失方式存储/加载(一旦编写,您不修改现有数组),这足以保证{{{{{{ 1}}(及其后面的行)适用于任何打电话System.arrayCopy()

的人

然而,构造仍然不是完全不透水的,因为通过refs.get()获取数组引用的任何人都可以继续并在不受refs.get()保护的情况下对元素进行更改

AtomicReference与此非常相似(它使用CopyOnWriteArrayListReentrantLock数组字段的组合而不是volatile),但它也保证没有人可以获得底层阵列并以不安全的方式使用它。

答案 1 :(得分:1)

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

   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以更安全的方式访问数组元素

答案 2 :(得分:1)

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

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

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