我想在内部了解如何在ConcurrentHashMap
和CopyOnWriteArrayList
等并发集合中处理并发修改异常。
互联网上有很多博客,建议使用这两种数据结构来避免并发修改异常。但没有解释,并发收集如何在内部处理此异常。
有人可以提供更多的见解吗?我需要一些详细的解释。
你问题的字面答案不是很有趣。 ConcurrentHashMap
和CopyOnWriteArrayList
不会抛出ConcurrentModificationException
,因为它们不包含抛出它的代码。
它不像ConcurrentModificationException
是一些低级内在的东西。 ArrayList
和HashMap
,以及其他收集课程,扔ConcurrentModificationException
来帮助你。它们必须包含额外的代码以尝试检测并发修改,以及额外的代码来引发异常。当其中一个类检测到某个地方存在导致对您的集合进行不安全修改的错误时,会抛出ConcurrentModificationException
。
支持安全并发修改的类不会抛出ConcurrentModificationException
,因为它们不需要。
如果您正在尝试调试ConcurrentModificationException
,还有很多其他问题可以帮助解决这个问题:
这是add()
和ArrayList
的CopyOnWriteArrayList
方法定义。
数组列表:
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++];
}
现在,这将回答CopyOnWriteArrayList如何避免对ConcurrentModificationException的需求。
修改集合时,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不会发生。