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

时间:2019-04-22 12:49:01

标签: java multithreading collections concurrentmodification copyonwritearraylist

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

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

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

3 个答案:

答案 0 :(得分:3)

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

这不像ConcurrentModificationException是一些底层的固有事物。 ArrayListHashMap,以及其他收集类中,抛出ConcurrentModificationException帮助您。它们必须包含额外代码,以尝试检测并发的修改,并包含额外的代码以引发异常。当其中一个类检测到某个地方存在错误而导致对您的集合进行不安全的修改时,会抛出ConcurrentModificationException

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

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

答案 1 :(得分:1)

这是add()ArrayList的{​​{1}}方法定义。

ArrayList:

CopyOnWriteArrayList

CopyOnWriteArrayList:

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

根据上面的代码,很明显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的代码,您会发现它在修改集合之前已锁定。 ArrayList的迭代器方法(例如structurally)也会检查修改,但CopyOnWriteArrayList的迭代器方法不会检查修改。例如:

ArrayList迭代器的next()方法:

next()/remove()

CopyOnWriteArrayList迭代器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];
    }

答案 2 :(得分:1)

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

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

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

那如何防止CME?标准集合中的CME仅作为迭代的结果抛出。如果在迭代集合时在同一集合实例上执行添加或删除操作,则会抛出该异常。

CopyOnWriteArrayList的迭代器将当前数组分配为集合的最终字段 快照 ,并将其用于迭代。如果另一个线程(甚至同一线程)尝试将其添加到CopyOnWriteArrayList,则更新将应用于新副本,而不是我们当前正在迭代的 快照 。< / p>

例如,我们知道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。