多线程清洗中直接在条件块修改全局集合属非同步写操作,即使加if判断也不具备并发安全性,易引发ConcurrentModificationException、数据残留或集合状态损坏;正确做法包括使用线程安全容器、单线程统一修改、不可变集合或显式同步。
在数据清洗的条件块中直接修改全局集合,本质仍是多线程环境下对共享可变状态的非同步写操作,和流程控制本身无关——条件判断(如 if (isinvalid(record)))只决定是否执行修改,但不提供任何并发保护能力。
条件块里的集合修改不是原子操作
即使你写的是 if (record.isDuplicate()) { globalList.remove(record); },这个过程仍被拆解为多个步骤:
- 读取
globalList的当前结构(获取 size、内部数组等) - 定位待删元素索引
- 移动后续元素、缩容、更新
modCount
这些步骤之间存在时间窗口。若两个线程几乎同时进入该if块,并发调用remove(),就可能触发: -
ConcurrentModificationException(单线程遍历中修改时常见,多线程下更易发生) - 元素实际未被删除(因索引错位或
modCount校验失败) - 集合内部状态损坏(如
ArrayList数组越界、size不一致)
全局集合是典型的共享可变资源
清洗任务常被拆成多线程并行处理(如用 ForkJoinPool 或 CompletableFuture),所有线程共用同一个 List、Set 或 Map 实例。此时:
- 没有锁(
synchronized、ReentrantLock)或线程安全容器(如CopyOnWriteArrayList、ConcurrentHashMap) -
remove()、add()、clear()等方法都不是线程安全的 - 即使条件逻辑再严谨(比如加了
volatile boolean cleaningStarted),也挡不住对集合本身的竞态访问
常见误用场景举例
// ❌ 危险:全局 list + 多线程清洗 + 条件中直接 remove
static List<Record> dirtyData = new ArrayList<>();
// ... 启动 5 个线程执行:
if (record.isExpired()) {
dirtyData.remove(record); // 多个线程同时调用 → 冲突
}结果可能是:
- 报
ConcurrentModificationException - 某些过期记录残留未删
-
dirtyData.size()返回异常值(如负数或远小于预期)
正确做法是隔离修改动作
- ✅ 使用线程安全集合:
ConcurrentLinkedQueue(适合生产者-消费者清洗流)、CopyOnWriteArrayList(读多写少) - ✅ 将修改收敛到单一线程:各工作线程把待删 ID 收集到
ThreadLocal<Set<Long>>,最后由主线程统一清理 - ✅ 改用不可变模式:每轮清洗生成新集合(
stream().filter(...).collect(...)),避免修改原集合 - ✅ 加显式同步:
synchronized(dirtyData) { dirtyData.remove(record); }(注意粒度与性能权衡)
不复杂但容易忽略。

















