同步方法中调用ArrayList.clear()仍触发ConcurrentModificationException原因排查
为啥加了
synchronized还会遇到ConcurrentModificationException? 别着急,这个问题其实挺常见的,我来给你拆解下核心原因:
1. 同步方法只保护自身代码块,其他非同步的ArrayList访问没被覆盖
你给方法加了synchronized,只能保证同一时间只有一个线程执行这个方法里的代码,但如果还有其他线程在别的地方(比如另一个没加锁的方法、或者直接在方法外)对同一个ArrayList做这些操作:
- 迭代遍历(比如用
for-each循环、显式调用iterator()) - 新增/删除元素(包括
add()、remove())
这些操作没有被同一个锁保护的话,就会和你的同步clear()操作产生并发冲突,触发ArrayList的快速失败机制,抛出ConcurrentModificationException。
举个直白的例子:线程A在执行你的同步clear()方法,同时线程B在另一个没加锁的方法里用for-each遍历这个ArrayList,这时候就会直接抛异常——因为synchronized只管自己的方法,管不到线程B的遍历操作。
2. 同步方法内部如果有迭代+修改的操作,单线程都能触发异常
哪怕是在你的同步方法里,如果先对ArrayList做了迭代(比如for (Object obj : list)),然后紧接着调用clear(),哪怕是同一个线程执行,也会抛出这个异常!因为ArrayList的迭代器是快速失败的,它会维护一个修改计数,迭代过程中如果集合被修改(包括clear()),就会立刻检测到并抛异常,这个和多线程没关系,是ArrayList本身的迭代特性。
比如这种代码,哪怕单线程跑都会炸:
synchronized void myMethod() { for (Object item : arrayList) { // 做点什么 } arrayList.clear(); // 这里会触发迭代器的修改检测,抛ConcurrentModificationException }
3. 锁的对象不统一,导致互斥失效
还有一种可能:你加synchronized的方法所在的对象,和其他线程操作ArrayList时用的锁不是同一个。比如你的ArrayList是类的成员变量,但其他线程操作它时,用的是另一个锁对象,那你的同步方法锁就起不到互斥作用。
比如这种情况:
public class MyClass { private ArrayList<Object> list = new ArrayList<>(); // 这个方法锁的是MyClass实例 synchronized void clearList() { list.clear(); } // 其他地方的代码,锁的是list本身 void anotherMethod() { synchronized (list) { list.add(new Object()); } } }
两个方法的锁不是同一个,还是会有并发冲突。
实用解决思路
给你几个靠谱的方案:
- 把所有访问这个ArrayList的操作(遍历、修改、clear)都用同一个锁保护起来:要么都用
synchronized方法(锁同一个对象),要么都显式锁ArrayList本身:synchronized(arrayList) { ... } - 换成线程安全的集合类:比如
CopyOnWriteArrayList,它的迭代器基于快照,不会因为并发修改抛异常;或者用Collections.synchronizedList()包装你的ArrayList,但要注意遍历的时候还是得手动加锁 - 如果在同步方法里需要迭代+修改,别用普通迭代器:改用
ListIterator的remove()方法,或者先把元素复制到临时集合再迭代,避免在迭代过程中修改原集合
内容的提问来源于stack exchange,提问作者Tirafesi
相关产品推荐
相关产品推荐

