parallel()和sequential()仅设置流的最终执行模式标记,以最后一次调用为准;中间操作不执行,反复调用无效,终端操作时才依据该标记决定并行或顺序执行。

不能靠多次调用 parallel() 和 sequential() 来“分段控制”执行模式。它们只影响整个流水线的最终执行方式,且以最后一次调用为准。
理解 parallel/sequential 的本质作用
这两个方法不是实时切换线程模型的开关,而是在流构建阶段设置一个布尔标记,告诉终端操作(如 collect、reduce)该用并行还是顺序方式执行。中间操作(filter、map 等)本身不触发执行,所以反复调用毫无意义。
-
stream.parallel().filter(...).sequential().map(...)→ 实际仍是顺序执行(因最后是sequential()) -
stream.sequential().map(...).parallel().reduce(...)→ 实际是并行执行(因最后是parallel()) - 真正起作用的是终端操作那一刻所“看到”的模式标记,而非过程中的任意一次调用
什么时候该用 parallel()?看数据特征和操作开销
并行流只有在满足一定条件时才带来收益,盲目启用反而拖慢速度。
- 数据量足够大(通常建议 ≥ 10⁴ 元素),否则拆分、合并、线程调度的开销会盖过并行收益
- 每个元素的处理逻辑较重(如含复杂计算、IO等待较少的纯CPU操作),轻量操作(如
x -> x + 1)几乎不值得并行 - 底层数据结构支持高效分割:ArrayList、HashMap、IntStream.range 等有良好
Spliterator;LinkedList、Stream.generate 则不适合 - 避免在流中混入同步块、共享可变状态或非线程安全操作(如
ArrayList::add),否则需额外加锁,抵消并行优势
更可控的替代方案:显式选择执行上下文
若需精细控制不同阶段的执行策略,不要依赖链式 parallel()/sequential(),而是拆分为独立子流或使用其他并发工具:
- 对同一数据源分别创建并行流和顺序流:
list.parallelStream().filter(...).collect(...)与list.stream().map(...).toList() - 计算密集型部分用
ForkJoinPool自定义线程池,避开commonPool的资源争抢 - 涉及 IO 或阻塞操作,优先考虑
CompletableFuture配合自定义ExecutorService,而非并行流 - 小批量但高频任务,用
Parallel.ForEach(C#)或 PowerShell 的ForEach-Object -Parallel -ThrottleLimit控制并发度,比无约束并行更稳妥
验证效果必须靠真实基准测试
是否提速不能凭经验猜测,必须用 JMH 等微基准工具实测。尤其注意:
- 预热充分(JVM JIT 编译会影响首轮结果)
- 每次只对比一种变量(如仅改 parallel/sequential,其余完全一致)
- 测量吞吐量(ops/s)或平均耗时,而非单次 wall-clock 时间
- 在目标硬件环境(核心数、内存带宽)下运行,避免开发机误判


















