Java Stream API - 理解并行流的额外开销

📅 发布时间:2026/7/2 22:06:42 👁️ 浏览次数:
Java Stream API - 理解并行流的额外开销
Java Stream API - 理解并行流的额外开销—— 并行不等于免费午餐 并行流的潜在收益是有代价的使用parallelStream()看似能提升性能但背后要付出不少额外开销数据必须被拆分成子任务子任务由多个线程并发执行所有子任务的结果需要合并⚠️ 如果你的任务本来就很轻量或者数据不适合拆分那么使用并行流反而会更慢 拆分数据的开销数据的拆分Splitting不是“白送的”它会消耗资源✅易于拆分的源比如ArrayList因为支持按索引随机访问❌难以拆分的源比如LinkedList只能顺序遍历拆分非常低效 举例ListInteger arrayList IntStream.range(0, 1_000_000) .boxed() .collect(Collectors.toCollection(ArrayList::new)); ListInteger linkedList IntStream.range(0, 1_000_000) .boxed() .collect(Collectors.toCollection(LinkedList::new)); long t1 System.nanoTime(); arrayList.parallelStream().map(x - x * 2).toList(); System.out.println(ArrayList time: (System.nanoTime() - t1)); long t2 System.nanoTime(); linkedList.parallelStream().map(x - x * 2).toList(); System.out.println(LinkedList time: (System.nanoTime() - t2)); 你会发现LinkedList表现很差因为拆分难⚙️ 合并结果的成本并行处理完后所有子线程都要把结果汇总回来这也有开销合并类型性能成本说明✅ 求和低每个线程只返回一个整数✅ 收集到 List低常见 Collector 支持高效合并⚠️ 合并 HashMap高存在键冲突时需要协调、加锁或合并逻辑示例合并 HashMapMapInteger, String map IntStream.range(0, 10_000) .parallel() .boxed() .collect(Collectors.toMap( i - i % 1000, // ❗会产生重复 key i - Val i, (v1, v2) - v1 / v2 // 手动合并冲突值 )); 如果你忘了写 merge 函数程序会抛出IllegalStateException 并发副作用的地雷区一旦你引入共享状态并行流的所有优势都可能崩塌ListInteger result new ArrayList(); IntStream.range(0, 1000) .parallel() .forEach(result::add); // ❌ 并发写入非线程安全集合☠️ 有副作用的代码不仅慢而且错得离谱 判断是否使用并行流的 4 条黄金法则 法则内容Rule #1不要为了“酷”而优化。只有在确实性能不达标时才考虑并行。Rule #2明智地选择数据源。避免使用不能高效拆分的结构如LinkedList。Rule #3不要修改外部状态不要共享可变状态。Rule #4不要猜性能请使用基准测试如 JMH来实测。 使用 JMH 快速比较并行与串行性能Benchmark public void serialSum() { IntStream.range(0, 1_000_000).sum(); } Benchmark public void parallelSum() { IntStream.range(0, 1_000_000).parallel().sum(); } 有些场景下串行甚至更快✅ 小结语并行不是“开个线程就能快”。你要考虑数据结构、任务粒度、副作用、合并成本。否则你是在用8核CPU完成一个人5秒能搞定的事还掉进了坑里