使用Iterator仍抛出ConcurrentModificationException的原因排查
问题分析与解决方案
兄弟,这个ConcurrentModificationException在多线程操作Map的时候太常见了,我来给你捋清楚原因和解决办法:
为什么会抛出这个异常?
首先得搞懂两个核心点:
Collections.synchronizedMap(new HashMap<>())只是给HashMap的单个方法加了同步锁,但复合操作(比如遍历+修改/添加)并不是原子的。也就是说,调用iterator()、hasNext()、next()这些方法时,虽然每个方法本身是同步的,但方法之间的间隙还是会让其他线程有机可乘。- HashMap的Iterator是fail-fast机制:当Iterator启动后,如果Map的结构被修改(比如添加、删除元素,除了Iterator自己的
remove()方法),Iterator就会抛出这个异常,防止你拿到不一致的数据。
结合你的场景拆解:
- 最初的代码里,你在遍历的时候同时调用了
it.remove()和unordered.remove(id)——这相当于用Iterator移除元素后,又用Map本身的方法再次修改结构,直接导致Map维护的modCount(结构修改次数)和Iterator预期的expectedModCount不一致,触发异常。 - 后来你删掉了
unordered.remove(id),但还是报错,这是因为你提到的遍历期间有其他线程在给unordered添加新元素。其他线程调用unordered.put()会修改Map的modCount,而当前遍历的Iterator没感知到这个变化,自然还是会抛出异常。
解决办法
方案1:换成ConcurrentHashMap(最推荐)
直接把Collections.synchronizedMap(new HashMap<>())换成ConcurrentHashMap<String, byte[]>(),这是最省心的方式:
Map<String, byte[]> unordered = new ConcurrentHashMap<>();
ConcurrentHashMap的Iterator是弱一致的,不会因为其他线程的修改而抛出ConcurrentModificationException,而且它本身就原生支持并发的读写、修改操作,完美适配你的多线程场景(服务器+客户端并发操作)。
方案2:给遍历+修改的整个过程加锁(如果不想换Map实现)
如果你非要用synchronizedMap,那必须手动给整个遍历和修改的代码块加锁,确保这段时间内没有其他线程能修改unordered的结构:
// 给unordered对象加锁,确保遍历期间其他线程无法修改它 synchronized(unordered) { int tam = 0; if (unordered.size() <= maxOrderSize) { tam = unordered.size(); } else { tam = maxOrderSize; } HashMap<String, byte[]> prop = new HashMap<>(tam); Iterator<String> it = unordered.keySet().iterator(); for (int i = 0; i < tam; i++) { if (it.hasNext()) { String id = it.next(); prop.put(id, unordered.get(id)); it.remove(); // 只能用Iterator的remove方法修改结构 } } }
这样,在锁持有期间,其他线程调用put()或者remove()都会被阻塞,直到当前线程完成遍历和修改,Iterator就不会检测到意外的结构变化了。
内容的提问来源于stack exchange,提问作者hunter32
相关产品推荐
相关产品推荐

