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