Java中使用CopyOnWriteArrayList的forEach方法是否会引发ConcurrentModificationException?
好问题!咱们得先从CopyOnWriteArrayList的核心设计理念——写时复制(Copy-On-Write)——说起,这是理解它所有并发行为的关键。
先看你贴出来的Java 11中forEach的实现:
public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); for (Object x : getArray()) { @SuppressWarnings("unchecked") E e = (E) x; action.accept(e); } }
你观察得很仔细:这个方法确实没有加任何锁,只是通过getArray()拿到当前数组的引用,然后直接遍历。那为什么并发执行add()或remove()这类写操作时,不会抛出ConcurrentModificationException呢?
原因就在CopyOnWriteArrayList的写操作逻辑上:当你调用写方法时,它并不会直接修改原数组,而是创建一个全新的数组副本,在这个新数组上完成添加/删除操作,最后再把原数组的引用替换成新数组。而forEach方法在调用getArray()的那一刻,就拿到了当时的数组快照——哪怕之后有写操作修改了数组引用,正在进行的遍历依然在操作旧数组,和新数组完全隔离,根本不会触发并发修改的检测。
你提到和iterator()方法的区别,其实两者本质是一致的:CopyOnWriteArrayList的迭代器同样是**fail-safe(安全失败)**的,它持有迭代器创建时的数组快照,遍历过程中也不会因为并发写操作抛出异常。只不过forEach是直接遍历快照数组,而iterator()是把快照封装成了迭代器对象而已。
总结一下:在调用forEach的同时并发执行写操作,绝对不会引发ConcurrentModificationException。因为遍历的是调用瞬间的数组快照,写操作只会修改新数组,两者互不干扰。
内容的提问来源于stack exchange,提问作者Vitalii

