CopyOnWriteArrayList和syncedList之间的区别

2022-09-01 12:47:56

根据我的理解,并发集合类优先于同步集合,因为并发集合类不会锁定整个集合对象。相反,它们在集合对象的一小部分上获取锁。

但是当我检查方法时,我们正在获取对完整集合对象的锁定。那么为什么比返回的列表更好呢?我在方法中看到的唯一区别是,每次调用该方法时,我们都会创建该数组的副本。addCopyOnWriteArrayListCopyOnWriteArrayListCollections.synchronizedListaddCopyOnWriteArrayListadd

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();
    }
}

答案 1

根据我的理解,并发集合类优先于同步集合,因为并发集合类不对整个集合对象进行锁定。相反,它对集合对象的一小部分进行锁定。

对于某些集合,情况确实如此,但不是全部。Collections.synchronizedMap 返回的映射在每个操作周围锁定整个映射,而 ConcurrentHashMap 仅锁定一个哈希存储桶用于某些操作,或者它可能对其他操作使用非阻塞算法。

对于其他集合,使用的算法以及因此而产生的权衡是不同的。与CopyOnWriteArrayList相比,Collections.synchronizedList返回的列表尤其如此。如您所见,在写入操作期间,两者都对整个数组进行锁定。那么,为什么会有所不同呢?synchronizedListCopyOnWriteArrayList

如果您查看其他操作(例如循环访问集合的每个元素),则会出现差异。的文档说,Collections.synchronizedList

用户在迭代返回的列表时必须手动同步该列表:

    List list = Collections.synchronizedList(new ArrayList());
    ...
    synchronized (list) {
        Iterator i = list.iterator(); // Must be in synchronized block
        while (i.hasNext())
            foo(i.next());
    }

不遵循此建议可能会导致非确定性行为。

换句话说,除非您手动进行锁定,否则迭代不是线程安全的。请注意,使用此技术时,此列表中其他线程的所有操作(包括迭代、获取、设置、添加和删除)都将被阻止。一次只有一个线程可以对此集合执行任何操作。synchronizedList

相比之下,文档说:CopyOnWriteArrayList

“快照”样式迭代器方法使用对创建迭代器时数组状态的引用。此数组在迭代器的生命周期内永远不会更改,因此不可能进行干扰,并且迭代器可以保证不会抛出 。迭代器不会反映自创建迭代器以来对列表的添加、删除或更改。ConcurrentModificationException

此列表中其他线程的操作可以并发进行,但迭代不受任何其他线程所做的更改的影响。因此,即使写入操作锁定了整个列表,仍然可以提供比普通列表更高的吞吐量。(前提是写入的读取和遍历比例很高。CopyOnWriteArrayListsynchronizedList


答案 2

对于写入(添加)操作,CopyOnWriteArrayList使用ReentrantLock并创建数据的备份副本,并且底层易失性数组引用仅通过setArray更新(在setArray之前对列表的任何读取操作都将在添加之前返回旧数据)。此外,CopyOnWriteArrayList提供快照故障安全迭代器,并且在写入/添加时不会抛出 ConcurrentModifficationException。

但是当我检查CopyOnWriteArrayList的add方法.class时,我们正在获取完整集合对象的锁定。那么为什么CopyOnWriteArrayList比syncedList更好。我在 CopyOnWriteArrayList 的 add 方法中看到的唯一区别是,每次调用 add 方法时,我们都会创建该数组的副本。

  1. 否,锁不在整个集合对象上。如上所述,它是一个重入锁,它与固有的对象锁不同。
  2. add 方法将始终创建现有数组的副本,并对副本进行修改,然后最终更新数组的易失性引用以指向此新数组。这就是为什么我们有“CopyOnWriteArrayList”这个名字 - 当你写进去时会复制。这也避免了并发修改异常