目录
- 前言
- 一、最简单理解
- 二、举个最简单例子
- 三、parallelStream底层是什么?
- 四、parallelStream什么时候适合?
- 五、什么时候不适合?
- 六、为什么IO场景不适合?
- 七、parallelStream的优势
-
- [1. 开发简单](#1. 开发简单)
- [2. 自动线程调度](#2. 自动线程调度)
- [3. CPU利用率高](#3. CPU利用率高)
- 八、parallelStream的缺点
- 九、parallelStream为什么有时反而更慢?
- 十、parallelStream不保证顺序
- 十一、真正高性能方案(推荐)
- [十二、parallelStream vs 线程池](#十二、parallelStream vs 线程池)
前言
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 | 线程池 |
|---|---|---|
| 简单 | 非常简单 | 较复杂 |
| 可控性 | 差 | 强 |
| 适合小功能 | 非常适合 | 一般 |
| 大型系统 | 不推荐滥用 | 推荐 |
| 自定义线程数 | 不方便 | 可以 |
| 隔离性 | 差 | 强 |