CopyOnWriteArrayList如何做到线程安全?

我已经查看了CopyOnWriteArrayList的OpenJDK源代码,似乎所有写入操作都受到相同锁的保护,而读取操作根本不受保护。据我所知,在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 像 1-2(new)-4-2(copyOf)-3 一样重新排序,则可能会观察到数组处于不一致的状态。get

我是否错误地理解了 JMM,或者对于为什么线程安全还有其他解释?CopyOnWriteArrayList


答案 1

如果查看基础数组引用,则会看到它被标记为 。当发生写入操作时(例如在上面的摘录中),此引用仅在最终语句中通过 更新。在此之前,任何读取操作都将从数组的旧副本返回元素。volatilevolatilesetArray

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

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


答案 2

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

数组引用本身被标记为,以便读者无需使用锁即可查看对引用数组的更改。(编辑:此外,保证分配不会重新排序,这将导致在数组可能处于不一致状态时完成分配。volatilevolatile

需要写锁定来防止并发修改,这可能会导致包含不一致数据的数组或更改丢失。


推荐