文章目录
并行的 Stream 流,即多线程执行的流。
1. 并行流的获取
- .parallelStream()方法,直接获取并行Stream流
- .parallel()方法,将串行流转成并行流
public class ParallelStream {
public static void main(String[] args) {
//方式一:通过.parallelStream()方法,直接获取并行Stream流
List<String> list = new ArrayList<>();
Stream<String> stream = list.parallelStream();
//方式二:通过.parallel()方法,将串行流转成并行流
Stream<String> parallelStream = list.stream().parallel();
//并行Stream流测试
Stream<Integer> stream = Stream.of(4, 5, 6, 3, 21, 56).parallel();
stream.filter(num->{
System.out.println(Thread.currentThread().getName() + "--" + num);
return num>3;
}).count();
}
}
输出(发现并行 Stream 流是在 多个线程上执行的):
ForkJoinPool.commonPool-worker-5--6
ForkJoinPool.commonPool-worker-3--4
main--3
ForkJoinPool.commonPool-worker-4--21
ForkJoinPool.commonPool-worker-1--5
ForkJoinPool.commonPool-worker-2--56
数组
String[] array = {"a", "b", "c", "d", "e"};
// 并行流
Stream<String> parallelStreamFromArray = Arrays.stream(array).parallel();
集合
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
// 并行流
Stream<Integer> parallelStreamFromSet = set.parallelStream();
2. 并行流存在线程安全问题
public class ParallelStreamTest05 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());
List<Integer> listNew = new ArrayList<>();
//使用并行流向集合中添加数据
list.parallelStream()
.forEach(s -> listNew.add(s));
System.out.println(listNew.size());
}
}
存在线程安全问题!!!
解决并行流线程安全问题:
2.1 加同步锁synchronized
public class ParallelStreamTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());
List<Integer> listNew = new ArrayList<>();
Object object = new Object();
//使用并行流向集合中添加数据.
//解决数据安全问题:1.加同步锁
list.parallelStream()
.forEach(s -> {
synchronized (object){
listNew.add(s);
}
});
System.out.println(listNew.size());
//另一种写法
List<Integer> list2 = new ArrayList<>();
Object object2 = new Object();
IntStream.range(0,1000)
.parallel()
.forEach(i -> {
synchronized (object2){
list2.add(i);
}
});
System.out.println(list2.size());
}
}
2.2 使用线程安全的容器synchronizedList
public class ParallelStreamTest06 {
public static void main(String[] args) {
//使用线程安全的容器
List<Integer> list3 = new ArrayList<>();
List<Integer> list3New = Collections.synchronizedList(list3);
Object obj = new Object();
IntStream.range(0,1000)
.parallel().forEach(s -> list3New.add(s));
System.out.println(list3New.size());
}
}
2.3 使用Stream流中的toArray或collect操作
public class ParallelStreamTest06 {
public static void main(String[] args) {
//通过Stream中的toArray或collect操作
List<Integer> list4 = new ArrayList<>();
List<Integer> collect = IntStream.range(0, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(collect.size());
}
}
3. 并行流的底层原理
之所以执行效率高,因为它底层使用的是 Fork/Join 框架。Fork/Join 框架 是在 JDK 7 中引入的,Fork/Join 框架可以将一个大任务拆分为很多小任务来异步执行。
4. 并行流使用注意事项
- 线程安全性:确保操作是线程安全的。在并行流中,多个线程会同时操作数据,因此需要确保操作的数据结构是线程安全的,或者采取适当的同步措施,避免出现并发修改异常或数据不一致的情况。
- 性能评估:并行流并不一定在所有情况下都能提升性能,有时甚至可能降低性能。在使用并行流之前,应该进行性能评估和测试,确保并行流确实能够带来性能的提升。
- 任务分解:并行流会将数据分成多个子任务,并行处理这些子任务。因此,在使用并行流时,应该注意任务的分解是否合理,避免出现任务分配不均衡的情况,导致部分线程长时间等待。
- 副作用:并行流中的操作可能会产生副作用,例如修改共享状态、I/O操作等。在使用并行流时,应该避免或者谨慎处理可能引起副作用的操作,以确保程序的正确性和可靠性。
- 并行度调整:Java并行流的并行度默认为CPU核心数,但可以通过调整系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 来修改默认的并行度。根据实际情况,可以根据计算密集型任务或I/O密集型任务来调整并行度,以获得更好的性能表现。
- 数据量大小:对于小规模的数据集,使用并行流可能会带来额外的开销,因为任务分解和线程管理的开销可能会抵消并行化带来的性能提升。因此,在处理小规模数据集时,可以考虑使用串行流。
- 避免共享可变状态:避免在并行流中共享可变状态,尽量使用不可变对象或线程安全的数据结构来处理数据,以避免线程安全问题。
5. 总结
- parallel 并行 Stream 流是 线程不安全 的;
- parallel 并行 Stream 流使用的场景是 CPU 密集型 的,只是做到别浪费 CPU,假如本身电脑的 CPU 的负载很大,那还到处用并行流,那并不能起到作用;
- I/O 密集型、磁盘I/O、网络I/O 都属于 I/O 操作,这部分操作时较少消耗 CPU 资源,一般并行流中 不适用于 I/O密集型 的操作,就比如使用并行流进行大批量的消息推送,涉及到了大量 I/O,使用并行流反而慢了很多;
- 在使用并行流的时候,是 无法保证元素的顺序 的,也就是即使你使用了同步集合也只能保证元素都正确,但无法保证其中的顺序。