list.parallelStream() 和 list.stream() 的主要区别在于并行处理能力
1. 核心区别
-
list.stream():顺序流,单线程顺序处理 -
list.parallelStream():并行流,自动使用多线程并行处理
2. 执行方式对比
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 顺序流 - 单线程顺序执行
numbers.stream()
.map(n -> {
System.out.println(Thread.currentThread().getName() + ": " + n);
return n * 2;
})
.forEach(System.out::println);
// 输出(大致):
// main: 1
// 2
// main: 2
// 4
// main: 3
// 6
// 并行流 - 多线程并行执行
numbers.parallelStream()
.map(n -> {
System.out.println(Thread.currentThread().getName() + ": " + n);
return n * 2;
})
.forEach(System.out::println);
// 输出(可能):
// ForkJoinPool.commonPool-worker-1: 3
// ForkJoinPool.commonPool-worker-2: 2
// main: 1
// 6
// 4
// 2
// ...
3. 性能特点
| 特性 | stream() |
parallelStream() |
|---|---|---|
| 线程使用 | 单线程 | ForkJoinPool 线程池 |
| 数据顺序 | 保持顺序 | 可能乱序(除非用forEachOrdered) |
| 适用场景 | 小数据量、顺序敏感操作 | 大数据量、CPU密集型计算 |
| 开销 | 低 | 有线程创建、同步开销 |
4.使用场景建议
适合使用 stream() 的情况:
java
// 1. 数据量小
List<String> smallList = ...;
smallList.stream()...
// 2. 需要保持顺序
list.stream()
.sorted() // 排序操作
.forEach(...); // 顺序重要
// 3. I/O密集型操作(文件读写、网络请求)
list.stream()
.map(this::readFromFile) // I/O操作通常不适合并行
.collect(Collectors.toList());
// 4. 有状态操作
list.stream()
.skip(2) // 跳过前两个
.limit(10) // 限制数量
.collect(...);
适合使用 parallelStream() 的情况
java
// 1. 大数据集(通常 > 10000个元素)
List<Integer> largeList = ...;
largeList.parallelStream()
.map(x -> heavyCalculation(x)) // 耗时计算
.collect(Collectors.toList());
// 2. CPU密集型计算
list.parallelStream()
.map(this::complexMathOperation) // 复杂数学运算
.reduce(0, Integer::sum);
// 3. 操作之间无依赖
list.parallelStream()
.filter(this::isValid) // 过滤可以并行
.map(this::transform) // 映射可以并行
.collect(Collectors.toList());
5. 重要注意事项
线程安全问题
java
List<Integer> result = Collections.synchronizedList(new ArrayList<>());
// ❌ 错误:并行修改共享状态
List<Integer> unsafeList = new ArrayList<>();
list.parallelStream()
.forEach(unsafeList::add); // 可能抛出ConcurrentModificationException
// ✅ 正确:使用线程安全集合或避免共享状态
list.parallelStream()
.collect(Collectors.toList()); // 使用内置收集器
顺序保证:
java
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 顺序不确定
list.parallelStream()
.forEach(System.out::print); // 可能输出:3 1 4 2 5
// 保持顺序
list.parallelStream()
.forEachOrdered(System.out::print); // 总是输出:1 2 3 4 5
6. 性能测试示例
java
public class StreamBenchmark {
public static void main(String[] args) {
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_0000)
.boxed()
.collect(Collectors.toList());
// 顺序流
long start = System.currentTimeMillis();
long sum1 = numbers.stream()
.mapToLong(i -> (long)i * i)
.sum();
long time1 = System.currentTimeMillis() - start;
// 并行流
start = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(i -> (long)i * i)
.sum();
long time2 = System.currentTimeMillis() - start;
System.out.println("顺序流时间: " + time1 + "ms");
System.out.println("并行流时间: " + time2 + "ms");
System.out.println("结果一致: " + (sum1 == sum2));
}
}
7. 最佳实践建议
-
先测试,再选择:对于关键代码,先测试两种方式的性能
-
默认用顺序流:除非有明确证据表明并行流更好
-
考虑NQ模型:只有当 N(数据量) × Q(每个元素计算量)较大时,并行才有效
-
避免副作用:并行流中避免修改外部状态
-
注意资源限制:并行流使用公共ForkJoinPool,可能影响其他并行任务
总结
-
stream():简单、可预测、适用于大多数场景 -
parallelStream():性能可能更好,但需要满足特定条件,且要注意线程安全和顺序问题
简单选择原则 :默认使用 stream(),只有当处理大数据集且操作耗时的情况下,才考虑使用 parallelStream() 并进行充分测试。