CopyOnWriteArrayList如何是线程安全的?

时间:2010-06-01 15:01:17

标签: java data-structures concurrency java-memory-model

我已经查看了OpenJDK source codeCopyOnWriteArrayList,似乎所有的写操作都受到同一个锁的保护,并且读取操作根本不受保护。据我所知,在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是线程安全的?

4 个答案:

答案 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实现了ArrayListVectorLinkedList之类的List接口,但是它是线程安全的集合,并且以与Vector稍有不同的方式实现了线程安全。或其他线程安全的收集类。

顾名思义,CopyOnWriteArrayList创建基础副本 带有每个突变操作的ArrayList添加或设置。一般 CopyOnWriteArrayList非常昂贵,因为它涉及昂贵的 阵列复制具有每个写入操作,但如果您进行复制,则非常有效 拥有一个列表,其中迭代次数超过了突变,例如你最需要 迭代ArrayList,不要经常修改它。

CopyOnWriteArrayList的迭代器是故障安全的,不会抛出 ConcurrentModificationException ,即使是基础 迭代开始后立即修改CopyOnWriteArrayList,因为 迭代器正在ArrayList的单独副本上运行。因此所有 在CopyOnWriteArrayList上进行的更新不适用于Iterator。

要获取最新版本,请重新读list.iterator();

话虽如此,大量更新此集合会降低性能。如果您尝试对CopyOnWriteArrayList进行排序,则会看到列表抛出UnsupportedOperationException(排序调用集合上设置的N次)。仅当您的读数超过90%以上时,才应使用此读数。