Java parallelStream并行流

目录

前言

parallelStream() 是 Java 8 Stream API 提供的 并行流(Parallel Stream)

它可以让:

  • 一个集合的处理逻辑
  • 自动使用多线程并行执行

而不是单线程。


一、最简单理解

普通 stream:

java 复制代码
list.stream()

是 单线程

例如:

java 复制代码
1个线程处理20万数据

parallelStream:

java 复制代码
list.parallelStream()

是 多线程并行处理

例如:

text 复制代码
8核CPU
可能8个线程同时处理

二、举个最简单例子

普通 stream :

java 复制代码
list.stream()
    .map(x -> doSomething(x))
    .collect(Collectors.toList());

执行过程:

text 复制代码
第1条
第2条
第3条
...

按顺序一个个执行。


parallelStream :

java 复制代码
list.parallelStream()
    .map(x -> doSomething(x))
    .collect(Collectors.toList());

执行过程:

text 复制代码
线程1 -> 第1万条
线程2 -> 第2万条
线程3 -> 第3万条
...

同时执行。


三、parallelStream底层是什么?

底层使用: ForkJoinPool

默认:

text 复制代码
CPU核心数 - 1

例如:

8核CPU:

text 复制代码
默认7个工作线程

四、parallelStream什么时候适合?

适合:CPU密集型任务

例如:

  • 大量计算
  • 加密
  • 哈希
  • 复杂规则计算
  • 图片处理
  • 金额计算
  • 风控计算

例如:

java 复制代码
list.parallelStream()
    .map(this::calculate)

计算 很耗CPU。这时收益巨大。


五、什么时候不适合?

IO密集型

例如:

text 复制代码
数据库
Feign
HTTP
Redis
文件

例如:

java 复制代码
list.parallelStream()
    .forEach(x -> feign.call());

通常会更慢,甚至打爆数据库/接口。


六、为什么IO场景不适合?

因为,parallelStream 默认线程数:

text 复制代码
CPU核数

而IO:

text 复制代码
大量时间在等待

CPU其实没干活。


比如:

text 复制代码
线程1 等数据库
线程2 等网络
线程3 等Redis

CPU闲着。

所以:

parallelStream 对IO提升有限。


七、parallelStream的优势

1. 开发简单

不用:

text 复制代码
线程池
Future
CountDownLatch
CompletableFuture

一行搞定。


2. 自动线程调度

自动:

  • 分片
  • 分任务
  • 合并结果

3. CPU利用率高

例如:

8核CPU:

text 复制代码
单线程只用了1核

parallelStream:

text 复制代码
8核一起跑

八、parallelStream的缺点

很多人乱用。


缺点1:线程不受控

默认:

text 复制代码
ForkJoinPool.commonPool()

全局共享线程池。

可能:

text 复制代码
影响整个系统

缺点2:不适合数据库/Feign

例如:

java 复制代码
parallelStream()
    .forEach(x -> mapper.insert(x));

危险。

可能 瞬间几千SQL。


缺点3:线程安全问题

例如:

你这样写:

java 复制代码
List<String> result = new ArrayList<>();

list.parallelStream().forEach(x -> {
    result.add(x);
});

错误!

因为:

text 复制代码
ArrayList线程不安全

可能:

  • 数据丢失
  • 数组越界
  • ConcurrentModificationException

正确写法

用:

java 复制代码
collect(Collectors.toList())

因为 collect 内部处理了并发。


九、parallelStream为什么有时反而更慢?

因为:线程切换有成本

例如:

java 复制代码
1+1

这种极小任务。

还没切线程快。


所以:数据量小不要用

经验:

数据量 建议
<1000 通常没必要
1万+ 开始有收益
10万+ 常有明显收益

十、parallelStream不保证顺序

例如:

java 复制代码
list.parallelStream()
    .forEach(System.out::println);

输出:

text 复制代码
乱序

如果需要顺序:

java 复制代码
forEachOrdered()

但:性能会下降。


十一、真正高性能方案(推荐)

很多大系统:

不会直接用:

text 复制代码
parallelStream

而是:

自定义线程池 + CompletableFuture

因为:

  • 可控
  • 可监控
  • 可限流
  • 可隔离

例如:

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(8);

CompletableFuture.supplyAsync(() -> {
    return calculate();
}, pool);

十二、parallelStream vs 线程池

对比 parallelStream 线程池
简单 非常简单 较复杂
可控性
适合小功能 非常适合 一般
大型系统 不推荐滥用 推荐
自定义线程数 不方便 可以
隔离性

相关推荐
Yeats_Liao5 分钟前
14:Servlet中的页面跳转-Java Web
java·后端·架构
未秃头的程序猿10 分钟前
告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍
java·后端·面试
鹤望兰67533 分钟前
字节跳动国际支付-后端开发-三面面经
java
Flittly40 分钟前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
RainCity44 分钟前
Java Swing 自定义组件库分享(十二)
java·笔记·后端
吃饱了得干活17 小时前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
lwx5728018 小时前
探秘InnoDB:搞懂它的内存、线程、磁盘与日志刷盘策略
java·后端
Flynt20 小时前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
plainGeekDev21 小时前
Activity 间传值 → Navigation 参数
android·java·kotlin
plainGeekDev21 小时前
onActivityResult → ActivityResult API
android·java·kotlin