parallel() 仅设置isParallel标志并切换至ForkJoinPool.commonPool()执行,不自动处理数据拆分、线程安全与共享状态竞争,误用易致ConcurrentModificationException、结果错乱或线程耗尽。

parallel() 不是万能加速器,先看它到底改了什么
调用 Stream.parallel() 本质是把底层 Stream 的 isParallel 标志设为 true,并切换到 ForkJoinPool.commonPool() 执行。它不会自动拆分数据、不保证线程安全、也不处理任何共享状态竞争——这些全得你来兜底。
常见错误现象:ConcurrentModificationException、结果乱序、计数不准、甚至 JVM 线程耗尽卡死。根本原因:把串行思维直接套到并行流上,误以为“加个 parallel() 就变快”。
哪些操作适合开 parallel(),哪些一开就翻车
适合的场景必须满足:无状态、无副作用、可分割、计算密集。典型例子是数值聚合、字符串批量清洗、JSON 解析后字段提取等。
- ✅ 安全:
stream.map(x -> x * x).filter(x -> x > 100).reduce(0, Integer::sum) - ❌ 危险:
stream.forEach(System.out::println)(输出顺序不可控,且System.out是共享资源) - ❌ 危险:
stream.map(x -> { counter++; return x; }).collect(...)(counter非原子变量) - ⚠️ 谨慎:含 I/O 的操作(如
Files.lines().parallel().map(this::callHttp)),线程池会被阻塞拖垮
commonPool() 线程数不是你想改就能改,得绕道
ForkJoinPool.commonPool() 默认线程数 = Runtime.getRuntime().availableProcessors() - 1,且**无法通过 API 直接修改**。强行改系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 只在 JVM 启动时有效,运行中无效。
真正可控的做法是脱离 commonPool,自己构造专用线程池:
ForkJoinPool pool = new ForkJoinPool(4);
List<String> result = pool.submit(() ->
list.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList())
).join();
pool.shutdown();
注意:parallelStream() 默认仍走 commonPool;必须用 ForkJoinPool.submit() + 显式 join() 才能绑定到自定义池。
性能反模式:小数据 + 简单操作 + parallel() = 负优化
并行有调度开销:任务切分、工作窃取、结果合并。对几千以内元素或纯内存简单运算,串行反而更快。实测过 List<Integer> of size 1000 做 map(x->x+1),并行比串行慢 2–3 倍。
判断是否值得并行,看两个硬指标:
- 单个元素处理耗时 > 100μs(比如一次本地 DB 查询、一次正则匹配)
- 集合大小 > 10000,且元素处理逻辑存在明显 CPU 密集特征
不确定时,先用 System.nanoTime() 对比两版实测,别猜。
最常被忽略的一点:并行流的终止操作(如 findFirst、anyMatch)在找到结果后**不会立即停止所有线程**,而是依赖 fork-join 的取消机制,实际仍有延迟。真要快速中断,得用带超时的 CompletableFuture 自己编排。


















