java并发集合虽解决线程安全问题,但误用仍致concurrentmodificationexception、unsupportedoperationexception、死锁或数据丢失;需从报错位置、原因及修复三层面排查。

Java 中并发集合本身就是为了规避普通集合在多线程下的线程安全问题而设计的,但“用了并发集合”不等于“不会报错”。常见报错往往不是集合本身出错,而是误用、边界场景或与其他机制(如迭代、自定义逻辑)配合不当导致。排查解决要从“哪里报错”“为什么报错”“怎么改”三层入手。
一、ConcurrentModificationException 仍出现
很多人以为用了 CopyOnWriteArrayList 或 ConcurrentHashMap 就绝对安全,结果在 foreach 遍历时删元素还是抛出异常——其实这是对并发集合能力的误解。
-
CopyOnWriteArrayList:遍历时允许增删,但它的迭代器是快照式的,删除操作不影响当前迭代,也不会触发 CME;但如果你在遍历中调用的是
list.remove()(而非iterator.remove()),且该 list 实际是普通 ArrayList 的伪装(比如被Arrays.asList()包装后又强转),就会出错。 -
ConcurrentHashMap:它的迭代器是弱一致性的,允许并发修改,不会抛 CME;但如果你用
for (Entry e : map.entrySet())遍历时,又在循环体里调用map.remove(key),某些旧 JDK 版本(如 Java 7)可能因内部结构变化触发异常;Java 8+ 已基本修复,但仍建议避免混用。 - 排查重点:检查报错堆栈中的实际类名(是不是你以为的并发集合?)、是否在增强 for 循环里直接调用了集合自身的修改方法、是否集合被包装或代理过(如 MyBatis 返回的 List 可能是不可变视图)。
二、UnsupportedOperationException 报错
这不是并发问题,而是可变性误判。典型场景:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 把
Collections.unmodifiableList(new CopyOnWriteArrayList())当作可修改集合使用; - 用
Map.copyOf(map)或List.of()创建集合后,误以为它支持 add/remove; - MyBatis 查询返回的
resultList是只读视图(尤其搭配@Select+resultType时),调用clear()或set()就会抛此异常。 - 解决办法:打印集合实际类型(
list.getClass().getName()),确认是否为CopyOnWriteArrayList、ConcurrentHashMap等真正可变并发类;避免对工厂方法(List.of、Collections.emptyXXX)返回值做修改操作。
三、死锁或长时间阻塞(看似“没报错”,实则卡住)
并发集合虽免除了外部加锁,但部分操作仍有隐式同步或重试逻辑。例如:
- ConcurrentHashMap.computeIfAbsent() 中传入的 mappingFunction 若执行耗时或又去操作同一个 map,可能引发线程阻塞甚至死锁(尤其在递归调用或锁升级场景);
- ConcurrentLinkedQueue.poll() 在空队列时是无锁非阻塞的,但如果上层代码写成 while(!queue.isEmpty()) { queue.poll(); },在高并发下可能因可见性问题陷入假空循环;
- 排查手段:用
jstack -l <pid></pid>查看线程状态,关注 RUNNABLE 状态但 CPU 占用异常高(可能是活锁),或 BLOCKED/WAITING 状态集中在某个并发集合的方法上(如CHM.transfer());结合 JFR(Java Flight Recorder)抓取锁竞争和方法热点。
四、数据丢失或结果不符合预期(静默错误)
没有异常,但业务逻辑出错。例如:
- 用
ConcurrentHashMap存计数器,多个线程同时执行map.put(key, map.getOrDefault(key, 0) + 1),结果少加——因为 get 和 put 不是原子的; - 用
CopyOnWriteArrayList做任务队列,生产者频繁 add,消费者遍历处理,但由于迭代器是快照,新增任务对本次遍历不可见,造成“延迟感知”,若业务要求强实时性就出问题; - 解决方案:用
CHM.merge()或compute()替代手动 get-put;对实时性敏感场景,改用BlockingQueue或ConcurrentLinkedDeque;关键业务逻辑加单元测试,模拟多线程并发调用验证最终状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










