核心是避免共享写操作,改用声明式收集或线程安全结构;并行流不保证线程安全,应优先用collect替代forEach+外部集合,使用flatMap处理多输出,自定义逻辑时实现Collector接口,必要时选用原子类或并发容器。

核心是避免共享写操作,改用声明式收集或线程安全结构。并行流本身不保证线程安全,它只是把任务分发到多个线程执行——如果这些线程共用同一个 ArrayList 或普通 int 变量,结果就不可靠。
优先用 collect 替代 forEach + 外部集合
这是最推荐、最简洁、也最符合函数式风格的做法。不要手动创建空集合再往里 add,而是让流自己分段收集、最后合并:
- ✅ 正确写法:
list.parallelStream().map(String::toUpperCase).collect(Collectors.toList()) - ❌ 错误写法:
List<String> result = new ArrayList<>(); list.parallelStream().forEach(s -> result.add(s.toUpperCase())); - Collectors 提供的收集器(如 toList、toSet、groupingBy)内部已适配 ForkJoinPool 分治模型,每个线程操作独立子容器,合并过程无竞争
需要生成多个元素时,用 flatMap 统一收口
当一个输入要映射为多个输出(比如拆分字符串、解析嵌套结构),别在 forEach 里反复 add,而应展开为流再 collect:
- 正确示例:
.flatMap(s -> Stream.of(s + "_sigma", s + "_lambda")) .collect(Collectors.toList()) - 整个链路无共享变量、无副作用,天然支持并行
真要自定义逻辑?实现 Collector 接口
内置收集器不够用时(例如需按自然顺序去重、或聚合到 TreeMap),不要退回到 synchronized 块或 Collections.synchronizedList,而是正确定义线程安全的 Collector:
立即学习“Java免费学习笔记(深入)”;
- Supplier:提供每个线程的局部容器,如
() -> new TreeSet<>() - BiConsumer:定义如何累加元素到局部容器
- BinaryOperator:定义如何合并两个局部容器
- Collector 自动处理分段、并发、合并,无需手写锁或 volatile
实在要用外部状态?选原子类或并发容器
极少数场景下必须累积状态(如计数、汇总),可选用线程安全类型,但要注意性能代价:
- 计数/求和:用
AtomicInteger、AtomicLong,比 synchronized 更轻量 - 集合写入:可用
ConcurrentHashMap或CopyOnWriteArrayList,但后者适合读多写少,频繁 add 性能较差 - 不建议用
Collections.synchronizedList(new ArrayList<>())—— 虽线程安全,但所有操作串行化,几乎抵消并行收益


















