Java中的volatile数组?

26
如何将数组变为volatile?因为据我所知,将数组变为volatile是不安全的。

2
http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html - Peter Knego
5个回答

24

声明数组为 volatile 并不会使其字段具有 volatile 访问权限。你所声明的是引用本身是 volatile,而不是它的元素。
换句话说,你声明了一个“具有 volatile 元素集合”而不是“一组具有 volatile 元素”的东西。

解决方法是如果要使用整数,请使用 AtomicIntegerArray。另一种方法(但有点丑陋)是每次编辑字段时重新编写数组的引用。

你可以通过以下方式实现:

arr = arr; 

(就像我说的...很丑)


2
我认为元素是易失性的并不意味着对元素的访问是原子级别的。 - Blindy
1
你是对的,volatile 关键字确保 happens-before 关系。 - mike
3
我不认为这是解决问题的方法。问题在于(就我理解的一部分而言),缓存一致性以及字节码系统和JVM本身的定义中几乎完全缺乏强内存顺序语义。重写数组引用不能保证你清晰地刷新任何你写入到缓存或内存中的数组元素,以便所有线程都对元素有相同的视图。 - Luke Hutchison
@LukeHutchison 实际上我认为这样做可能是正确的(虽然非常不优雅)。不过我宁愿每次将 volatile 数组字段复制到一个私有变量中(privArr = volatileArr; 然后对 privArr 进行操作)。任何 volatile 访问都确保发生在所有其他访问之前。但是,这种 hack 无法获得原子更新。 - Stefan Reich

12

AtomicLongArray、AtomicIntegerArray和AtomicReferenceArray(java.util.concurrent.atomic)。


12

编辑: 在Java中,数组是对象。如果将对该对象的引用设置为volatile,则交换对数组的引用时,其他线程可以看到它。 但是,这并不适用于数组值本身。

更好地理解Java内存模型,实际上有可能避开使用Atomic*Array。使用volatile读取和正常写入的先发生-后发生关系可以实现:

如果线程A写入一些非volatile的内容以及一个volatile变量之后,只有当线程B首先读取volatile变量时,才能保证线程B也看到非volatile内容的更改。 另请参见: Happens-before relationships with volatile fields and synchronized blocks in Java - and their impact on non-volatile variables?

对于数组,这意味着: 在写入数组后,写入某个volatile状态变量(确保写操作实际上更改了volatile状态变量!) 在从数组读取时,首先读取volatile状态变量,然后再访问数组。 只要之前已经发生过,volatile读操作应该使所有其他写操作都可见。

旧版: 写入自身引用arr=arr实际上没有帮助。

您写入的是数组的地址arr,而不是字段arr[i]的值。因此,您仍然不能获得arr[i]的volatile属性(这是您想要的),但只能获得存储地址arr的volatile属性。

Jeremy Manson的上述博客文章详细解释了这一点: http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html

他最好的解决方案是使用Atomic*Array,即对于通用类型使用AtomicReferenceArray(也存在基本类型的特殊形式)。我想象不出这特别高效,尤其是因为它可以提供比您需要的更多的属性(原子性>>volatile)。

另一个选择是使用指针结构,其中容器使用易失性指针字段。但这种方法效率不高...


3
这个怎么样:
static class Cell<T> {
        volatile T elem;
    }

private Cell<T>[] alloc(int size){
        Cell<T>[] cells = (Cell<T>[]) (new Cell[size]);
        return cells;
    }

 volatile Cell<T>[] arr;
 Cell<T>[] newarr = alloc(16);
 for (int i = 0; i < newarr.length; i++) {
      newarr[i] = new Cell<>();
 }
 arr = newarr;

这些单元格使内容也变得不稳定。在预先分配单元格后,我将新数组赋给了易挥发的数组…这就牺牲了额外的单元格内存,但是这是可以管理的。


有趣的想法。 - Ceki

2
Java9引入了一种通用的方法来处理volatile数组:VarHandle,允许对任何类型的数组进行原子操作。
以下是文档中的一个自我描述示例:
String[] sa = ...
VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
boolean r = avh.compareAndSet(sa, 10, "expected", "new");

值得一提的是,自Java9起,“concurrent”包中的原子类型(如AtomicInteger、AtomicIntegerArray、AtomicReference等)使用VarHandle而不是Unsafe来实现平台相关的操作,比如“CompareAndSet”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接