3

我想在内部了解如何在 和 等并发集合中处理并发修改ConcurrentHashMap异常CopyOnWriteArrayList

互联网上有很多博客建议使用这两种数据结构来避免并发修改异常。但没有任何解释,并发收集如何在内部处理此异常。

有人可以对此提供更多见解吗?我需要一些详细的解释。

4

3 回答 3

4

你的问题的字面答案不是很有趣。ConcurrentHashMap并且CopyOnWriteArrayList不要抛出ConcurrentModificationException,因为它们不包含抛出它的代码。

它不像ConcurrentModificationException是一些低级的内在事物。ArrayList并且HashMap,在其他收集类中,投掷ConcurrentModificationException帮助您。它们必须包含额外的代码来尝试检测并发修改,以及额外的代码来引发异常。ConcurrentModificationException当其中一个类检测到某处存在导致对您的集合进行不安全修改的错误时抛出。

支持安全并发修改的类不会抛出ConcurrentModificationException,因为它们不需要。

如果您尝试调试 a ConcurrentModificationException,还有很多其他问题可以帮助您回答:

于 2019-04-22T13:40:34.347 回答
1

这是and的add()方法定义。 ArrayListCopyOnWriteArrayList

数组列表:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

CopyOnWriteArrayList:

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

从上面的代码可以清楚地看出,CopyOnWriteArrayList在修改地图之前需要锁定。在这里,我刚刚发布了该add方法的代码。如果您查看remove()/addAll()或任何代码method which modifiesList structurally您可以看到它在修改集合之前需要锁定。ArrayList 的迭代器方法如next()/remove()检查修改,但对于 CopyOnWriteArrayList 的迭代器方法不检查修改。例如 :

ArrayList 迭代器 next() 方法:

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

CopyOnWriteArrayList 迭代器 next() 方法:

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
于 2019-04-22T13:01:36.440 回答
1

现在,这将回答 CopyOnWriteArrayList 如何避免需要 ConcurrentModificationException。

当您修改集合时, CopyOnWriteArrayList 会做两件事

  1. 它可以防止其他线程通过锁定修改集合
  2. 将当前 CopyOnWriteArrayList 中的所有元素复制到一个新数组中,然后将该新数组分配给该类的数组实例

那么这如何防止CME呢?标准集合中的 CME 只会作为迭代的结果而被抛出。如果在对集合进行迭代时,在同一个集合实例上执行添加或删除,则会引发异常。

CopyOnWriteArrayList 的迭代器将当前数组分配为集合的最终字段快照,并将其用于迭代。如果另一个线程(甚至是同一个线程)尝试添加到 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();
    }
}

注意线程本地 newElements 赋值,完成后它将设置为类实例volatile数组。

然后是迭代器,它被定义为

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

因此,在迭代时,我们正在读取任何修改之前的数组,并且由于没有其他线程可以修改快照,我们正在查看的 ConcurrentModificationException 不会发生。

于 2019-04-22T13:39:22.160 回答