我已经查看了OpenJDK source code的CopyOnWriteArrayList
,似乎所有的写操作都受到同一个锁的保护,并且读取操作根本不受保护。据我所知,在JMM下,所有对变量的访问(读取和写入)都应该受到锁定或重新排序的影响。
例如,set(int, E)
方法包含这些行(在锁定下):
/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);
另一方面,get(int)
方法仅return get(getArray(), index);
。
在我对JMM的理解中,这意味着如果语句1-4被重新排序,get
可能会以不一致的状态观察数组,如1-2(新)-4-2(copyOf)-3。< / p>
我是否理解JMM不正确或是否有任何其他解释为什么CopyOnWriteArrayList
是线程安全的?
答案 0 :(得分:68)
如果查看基础数组引用,您会看到它标记为volatile
。当发生写操作时(例如在上面的提取中),此volatile
引用仅在最终语句中通过setArray
更新。到目前为止,任何读取操作都将返回数组的旧副本中的元素。
重要的一点是数组更新是原子操作,因此读取将始终看到数组处于一致状态。
仅为写入操作取出锁定的优点是提高了读取的吞吐量:这是因为CopyOnWriteArrayList
的写入操作可能非常慢,因为它们涉及复制整个列表。
答案 1 :(得分:18)
获取数组引用是一种原子操作。因此,读者将看到旧数组或新数组 - 无论哪种状态都是一致的。 (set(int,E)
在设置引用之前计算新的数组内容,因此在进行asignment时数组是一致的。)
数组引用本身标记为volatile
,因此读者不需要使用锁来查看引用数组的更改。 (编辑:另外,volatile
保证赋值不被重新排序,这将导致在数组可能处于不一致状态时完成赋值。)
需要写入锁以防止并发修改,这可能导致数组保持不一致的数据或更改丢失。
答案 2 :(得分:0)
因此,根据Java 1.8,以下是 CopyOnWriteArrayList 中的 array 和 lock 声明。
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
以下是 CopyOnWriteArrayList
的 add 方法的定义 public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
@Adamski已经提到 array 是易失的,只能通过setArray方法进行更新。之后,如果进行了所有只读调用,则它们将获得更新的值,因此数组在此始终保持一致。
答案 3 :(得分:-1)
CopyOnWriteArrayList是Java 5并发API中引入的并发Collection类,以及Java中流行的表亲ConcurrentHashMap
。
CopyOnWriteArrayList
实现了ArrayList
,Vector
和LinkedList
之类的List接口,但是它是线程安全的集合,并且以与Vector稍有不同的方式实现了线程安全。或其他线程安全的收集类。
顾名思义,CopyOnWriteArrayList创建基础副本 带有每个突变操作的ArrayList添加或设置。一般 CopyOnWriteArrayList非常昂贵,因为它涉及昂贵的 阵列复制具有每个写入操作,但如果您进行复制,则非常有效 拥有一个列表,其中迭代次数超过了突变,例如你最需要 迭代ArrayList,不要经常修改它。
CopyOnWriteArrayList的迭代器是故障安全的,不会抛出 ConcurrentModificationException ,即使是基础 迭代开始后立即修改CopyOnWriteArrayList,因为 迭代器正在ArrayList的单独副本上运行。因此所有 在CopyOnWriteArrayList上进行的更新不适用于Iterator。
要获取最新版本,请重新读list.iterator();
话虽如此,大量更新此集合会降低性能。如果您尝试对CopyOnWriteArrayList
进行排序,则会看到列表抛出UnsupportedOperationException
(排序调用集合上设置的N次)。仅当您的读数超过90%以上时,才应使用此读数。