CopyOnWriteArrayList如何是线程安全的?

Fix*_*int 56 java concurrency java-memory-model data-structures

我已经看了一下OpenJDK的源代码,CopyOnWriteArrayList似乎所有的写操作都受到同一个锁的保护,读操作根本就没有受到保护.据我所知,在JMM下,所有对变量(读取和写入)的访问都应该受到锁定或重新排序的影响.

例如,set(int, E)方法包含这些行(在锁定下):

/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);
Run Code Online (Sandbox Code Playgroud)

get(int)另一方面,该方法仅适用return get(getArray(), index);.

在我对JMM的理解中,这意味着get如果语句1-4被重新排序,如1-2(新)-4-2(copyOf)-3 ,则可能会观察到数组处于不一致状态.

我是否理解JMM不正确或是否有任何其他解释为什么CopyOnWriteArrayList线程安全?

Ada*_*ski 70

如果查看底层数组引用,您会看到它被标记为volatile.当发生写操作时(例如在上面的提取中),该volatile引用仅在最终语句中更新setArray.到目前为止,任何读取操作都将从数组的旧副本返回元素.

重要的一点是,数组更新是一个原子操作,因此读取将始终看到数组处于一致状态.

仅对写入操作取出锁定的优点是提高了读取的吞吐量:这是因为对于a的写入操作CopyOnWriteArrayList可能非常慢,因为它们涉及复制整个列表.

  • 一个重要的细节是volatile只适用于数组引用本身,而不适用于数组的内容.但是,因为对数组的所有更改都是在**之前发布的,所以它的引用被发布,所以volatile保证会扩展到数组的内容. (20认同)

mdm*_*dma 19

获取数组引用是一种原子操作.因此,读者将看到旧数组或新数组 - 无论哪种状态都是一致的.(set(int,E)在设置引用之前计算新的数组内容,因此在进行asignment时数组是一致的.)

数组引用本身被标记为volatile使得读者不需要使用锁来查看引用数组的更改.(编辑:另外,volatile保证不重新排序赋值,这将导致在数组可能处于不一致状态时完成赋值.)

需要写锁来防止并发修改,这可能导致数组保持不一致的数据或更改丢失.

  • 这是不一样的.读取/写入易失性是指JMM称之为"同步动作"并定义了可以重新排序的障碍.请参阅http://java.sun.com/docs/books/jls/third_edition/html/memory.html (3认同)
  • 这是一个要回答多少细节的问题。但是,由于这引起了至少一个人的困惑,因此我将更新我的答案以使其明确。 (2认同)