并行流是Java 8引入的强大特性,它能够自动将流操作并行化以利用多核处理器的优势。下面我们将全面探讨parallelStream的使用方法、原理和最佳实践。
一、并行流基础
1. 创建并行流
// 从集合创建并行流
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> parallelStream = list.parallelStream();
// 将顺序流转为并行流
Stream<String> parallelStream2 = Stream.of("a", "b", "c").parallel();
2. 基本使用示例
List<Integer> numbers = IntStream.rangeClosed(1, 100).boxed().collect(Collectors.toList());
// 并行计算平方和
long sum = numbers.parallelStream()
.mapToLong(i -> i * i)
.sum();
二、并行流工作原理
1. 底层机制
并行流使用Fork/Join框架实现:
- 将任务分割为多个子任务(fork)
- 并行执行这些子任务
- 合并结果(join)
2. 线程池配置
默认使用ForkJoinPool.commonPool()
:
- 线程数默认为
Runtime.getRuntime().availableProcessors() - 1
- 可自定义系统属性:
java.util.concurrent.ForkJoinPool.common.parallelism
// 设置全局并行度
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");
三、适用场景
1. 适合使用并行流的场景
- 数据量大:通常超过10,000个元素
- 计算密集型操作:如复杂的数学运算
- 无状态操作:如map、filter、flatMap等
- 独立操作:元素处理不依赖其他元素
2. 不适合的场景
- 顺序依赖操作:如limit、findFirst等
- 有状态操作:如sorted、distinct
- I/O密集型操作:可能导致线程阻塞
- 小数据集:并行开销可能超过收益
四、性能优化技巧
1. 正确测量性能
long start = System.nanoTime();
result = list.parallelStream().[...].collect(Collectors.toList());
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("耗时: " + duration + " ms");
2. 选择合适的并行度
// 自定义线程池
ForkJoinPool customPool = new ForkJoinPool(4);
customPool.submit(() -> {
list.parallelStream().[...].collect(Collectors.toList());
}).get();
3. 避免共享可变状态
// 错误示例 - 存在竞态条件
List<String> result = new ArrayList<>();
list.parallelStream().forEach(s -> result.add(s.toUpperCase())); // 可能抛出异常
// 正确做法
List<String> safeResult = list.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());
五、常见陷阱与解决方案
1. 线程安全问题
问题:
int[] counter = new int[1];
list.parallelStream().forEach(e -> counter[0]++); // 竞态条件
解决:
// 使用原子类
AtomicInteger counter = new AtomicInteger();
list.parallelStream().forEach(e -> counter.incrementAndGet());
// 或使用归约操作
int sum = list.parallelStream().mapToInt(e -> 1).sum();
2. 顺序敏感操作
问题:
// 并行流中findFirst可能不如预期
Optional<Integer> first = list.parallelStream()
.filter(i -> i > 10)
.findFirst();
解决:
// 如需顺序保证,使用顺序流
Optional<Integer> first = list.stream()
.filter(i -> i > 10)
.findFirst();
六、高级应用
1. 自定义Spliterator
class CustomSpliterator<T> implements Spliterator<T> {
// 实现方法...
}
Spliterator<String> spliterator = new CustomSpliterator<>(data);
Stream<String> parallelStream = StreamSupport.stream(spliterator, true);
2. 并行收集器
// 使用线程安全的收集器
Map<String, List<Student>> studentsByClass = students.parallelStream()
.collect(Collectors.groupingByConcurrent(Student::getClassName));
七、性能对比
测试示例
List<Integer> numbers = IntStream.rangeClosed(1, 10_000_000).boxed().collect(Collectors.toList());
// 顺序流
long seqTime = measureTime(() -> numbers.stream().reduce(0, Integer::sum));
// 并行流
long parTime = measureTime(() -> numbers.parallelStream().reduce(0, Integer::sum));
System.out.println("顺序流: " + seqTime + "ms");
System.out.println("并行流: " + parTime + "ms");
典型结果
操作 | 数据量 | 顺序流耗时 | 并行流耗时 |
---|---|---|---|
求和 | 100万 | 15ms | 8ms |
过滤 | 1000万 | 120ms | 45ms |
排序 | 100万 | 650ms | 750ms |
八、最佳实践总结
- 先测试后优化:不要假设并行一定更快,实际测量性能
- 避免副作用:确保lambda表达式没有副作用
- 考虑顺序性:需要顺序保证时使用顺序流
- 合理设置并行度:根据CPU核心数和任务特性调整
- 注意数据结构:ArrayList比LinkedList更适合并行处理
- 避免自动装箱:使用原始类型流(IntStream等)提升性能
并行流是强大的工具,但需要谨慎使用。正确使用时可以显著提升性能,错误使用则可能导致问题。理解其工作原理和适用场景是有效使用并行流的关键。