如何通过CopyOnWriteArrayList / ConcurrentHashMap在内部处理并发修改异常?

问题描述 投票:3回答:3

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

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

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

java multithreading collections concurrentmodification copyonwritearraylist
3个回答
3
投票

你问题的字面答案不是很有趣。 ConcurrentHashMapCopyOnWriteArrayList不会抛出ConcurrentModificationException,因为它们不包含抛出它的代码。

它不像ConcurrentModificationException是一些低级内在的东西。 ArrayListHashMap,以及其他收集课程,扔ConcurrentModificationException来帮助你。它们必须包含额外的代码以尝试检测并发修改,以及额外的代码来引发异常。当其中一个类检测到某个地方存在导致对您的集合进行不安全修改的错误时,会抛出ConcurrentModificationException

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

如果您正在尝试调试ConcurrentModificationException,还有很多其他问题可以帮助解决这个问题:


1
投票

这是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 modifies的代码List 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++];
    }

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不会发生。

© www.soinside.com 2019 - 2024. All rights reserved.