arraylist非线程安全,多线程中直接去重会导致数据错乱;应避免并发写入,改用parallelstream().distinct()(并行高效但不保序)或stream().distinct()(串行保序),依赖元素equals/hashcode,勿直接操作arraylist。

Java 中 ArrayList 本身不是线程安全的,直接在多线程流式处理(如 parallelStream())中用它做去重操作(比如调用 distinct() 或手动 add + contains)会导致数据错乱、丢失或抛出异常。正确做法不是“让 ArrayList 去支持并发去重”,而是**避开对非线程安全集合的并发写入**,改用线程安全的替代方案或合理设计流程。
用 parallelStream() + distinct()(推荐,最简单)
distinct() 是中间操作,底层基于 ConcurrentHashMap 实现去重逻辑(JDK 8+),天然支持并行流,无需手动同步:
List<string> list = Arrays.asList("a", "b", "a", "c", "b");
List<string> unique = list.parallelStream()
.distinct()
.collect(Collectors.toList()); // 结果顺序不保证(并行下)
</string></string>注意:
- ✅ 安全:无竞态,无需额外同步
- ⚠️ 顺序:并行流不保证元素顺序,若需保持原始顺序,改用 stream().distinct()(串行)
- ✅ 去重依据:调用元素的 equals() 和 hashCode()
需要保留首次出现顺序且用并行?用 ConcurrentHashMap + 计数标记
如果必须并行处理,又想按首次出现顺序收集唯一元素(例如“去重但保持原始位置”),可借助 ConcurrentHashMap 的原子性做“首次注册”判断:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
ConcurrentHashMap<string boolean> seen = new ConcurrentHashMap();
List<string> uniqueInOrder = list.parallelStream()
.filter(s -> seen.putIfAbsent(s, Boolean.TRUE) == null)
.sorted() // 若需排序;否则顺序仍不确定(因并行调度)
.collect(Collectors.toList());
</string></string>⚠️ 但注意:即使用了 putIfAbsent,parallelStream() 的终止操作(如 collect)仍无法保证遍历顺序与原列表一致。真正保序的并行去重在标准库中没有直接支持,实际建议:
- 先用
parallelStream().distinct().collect(...)得到去重结果 - 再用原始列表顺序筛选(轻量级二次处理):
original.stream().distinct().collect(...) - 或者用
LinkedHashSet手动控制(见下一条)
手动并发去重?避免直接操作 ArrayList,改用线程安全集合
不要在多个线程里同时 list.add() 或 list.contains() —— 这会破坏 ArrayList 内部数组状态。可行替代方式:
-
用
CopyOnWriteArrayList:适合读多写少、去重后追加次数极少的场景(每次 add 都复制数组,开销大,不推荐用于大量去重) -
用
ConcurrentSkipListSet或ConcurrentHashMap作临时缓冲:利用其线程安全性暂存唯一值,最后转成 List -
分段处理 + 合并去重:将原列表切分为子块,并行处理每块为
Set,最后合并所有 Set 并转回 List(适合大数据量)
总结关键点
✅ 正确姿势:
- 优先用 stream().distinct()(串行保序)或 parallelStream().distinct()(并行高效,不保序)
- 去重逻辑依赖 equals/hashCode,确保自定义对象已正确重写
- 不要试图在多线程中直接读写同一个 ArrayList 实现去重
- 如需复杂逻辑(如按字段去重、带条件过滤),封装进 filter 或用 Collectors.toMap 等线程安全收集器
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










