Java8 parallelStream浅析
JAVA8中引入了lamda表达式和Stream接口。其丰富的API及强大的表达能力极大的简化代码,提升了效率,同时还通过parallelStream提供并发操作的支持,本文探讨parallelStream方法的使用。
首先看下java doc中对parallelStream的定义。
A sequence of elements supporting sequential and parallel aggregate operations.
...
Stream pipelines may execute either sequentially or in parallel. This execution mode is a property of the stream.
Streams are created with an initial choice of sequential or parallel execution. (For example, Collection.stream()
creates a sequential stream, and Collection.parallelStream() creates a parallel one.)
This choice of execution mode may be modified by the BaseStream.sequential() or BaseStream.parallel() methods,
and may be queried with the BaseStream.isParallel() method.
既然可以并行的执行,废话不多说,先看一个例子。
class Person {
int id;
String name;
String sex;
float height;
public Person(int id, String name, String sex, float height) {
this.id = id;
this.name = name;
this.sex = sex;
this.height = height;
}
}
/**
* 构造数据
*
* @return
*/
public List<Person> constructPersons() {
List<Person> persons = new ArrayList<Person>();
for (int i = 0; i < 5; i++) {
Person p = new Person(i, "name" + i, "sex" + i, i);
persons.add(p);
}
return persons;
}
/**
* for
*
* @param persons
*/
public void doFor(List<Person> persons) {
long start = System.currentTimeMillis();
for (Person p : persons) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(p.name);
}
long end = System.currentTimeMillis();
System.out.println("doFor cost:" + (end - start));
}
/**
* 顺序流
*
* @param persons
*/
public void doStream(List<Person> persons) {
long start = System.currentTimeMillis();
persons.stream().forEach(x -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(x.name);
});
long end = System.currentTimeMillis();
System.out.println("doStream cost:" + (end - start));
}
/**
* 并行流
*
* @param persons
*/
public void doParallelStream(List<Person> persons) {
long start = System.currentTimeMillis();
persons.parallelStream().forEach(x -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(x.name);
});
long end = System.currentTimeMillis();
System.out.println("doParallelStream cost:" + (end - start));
}
执行结果:
name0
name1
name2
name3
name4
doFor cost:5021
name0
name1
name2
name3
name4
doStream cost:5076
name4
name0
name2
name3
name1
doParallelStream cost:1010
代码上 stream 和 parallelStream 语法差异较小,从执行结果来看,stream顺序输出,而parallelStream 无序输出;parallelStream 执行耗时是 stream 的五分之一。
可以看到在当前测试场景下,parallelStream 获得的相对较好的执行性能,那parallelStream背后到底是什么呢?
要深入了解parallelStream,首先要弄明白ForkJoin框架和ForkJoinPool。ForkJoin框架是java7中提供的并行执行框架,他的策略是分而治之。说白了,就是把一个大的任务切分成很多小的子任务,子任务执行完毕后,再把结果合并起来。

顺便说下ForkJoin框架和ThreadPoolExecutor的区别,ForkJoin框架可以使用数量有限的线程数,执行大量任务,并且这些任务之间是有父子依赖的,必须是子任务执行完成后,父任务才能执行。ThreadPoolExecutor 显然是无法支持这种场景的。而ForkJoin框架,可以让其中的线程创建新的任务,并挂起当前的任务,任务以及子任务会保留在一个内部队列中,此时线程就能够从队列中选择任务顺序执行。
Java 8为ForkJoinPool添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。它是ForkJoinPool类型上的一个静态元素,它拥有的默认线程数量等于运行计算机上的处理器数量。当调用Arrays类上添加的新方法时,自动并行化就会发生。比如用来排序一个数组的并行快速排序,用来对一个数组中的元素进行并行遍历。自动并行化也被运用在Java 8新添加的Stream API中。
上面的代码中,forEach方法会为每个元素的操作创建一个任务,该任务会被前文中提到的ForkJoinPool中的通用线程池处理。以上的并行计算逻辑当然也可以使用ThreadPoolExecutor完成,但是就代码的可读性和代码量而言,使用ForkJoinPool明显更胜一筹。
默认线程池的数量就是处理器的数量,特殊场景下可以使用系统属性:-Djava.util.concurrent.ForkJoinPool.common.parallelism={N} 调整。
对上面例子做下调整,sleep时间变为2ms,
Thread.sleep(2);
执行结果如下:
doFor cost:12
=======================
doParallelStream cost:62
=======================
doStream cost:13
doParallelStream耗时最多,可见并不是并行执行就是性能最好的,要根据具体的应用场景测试分析。这个例子中,每个子任务执行时间较短,而线程切换消耗了大量时间。
说到了并发,不得不提线程安全。先看一个例子:
public void doThreadUnSafe() {
List<Integer> listFor = new ArrayList<>();
List<Integer> listParallel = new ArrayList<>();
IntStream.range(0, 1000).forEach(listFor::add);
IntStream.range(0, 1000).parallel().forEach(listParallel::add);
System.out.println("listFor size :" + listFor.size());
System.out.println("listParallel size :" + listParallel.size());
}
输出结果:
listFor size :1000
listParallel size :949
显而易见,stream.parallel.forEach()中执行的操作并非线程安全。如果需要线程安全,可以把集合转换为同步集合,即:Collections.synchronizedList(new ArrayList<>())。
总结下来如下:
- 使用parallelStream可以简洁高效的写出并发代码。
- parallelStream并行执行是无序的。
- parallelStream提供了更简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。
- 任务之间最好是状态无关的,因为parallelStream默认是非线程安全的,可能带来结果的不确定性。
参考:
https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html
https://docs.oracle.com/javase/8/docs/api/index.html
https://blog.csdn.net/u011001723/article/details/52794455/
摘自:https://zhuanlan.zhihu.com/p/43039062
Java8 parallelStream浅析的更多相关文章
- 【Java】关于Java8 parallelStream并发安全的思考
背景 Java8的stream接口极大地减少了for循环写法的复杂性,stream提供了map/reduce/collect等一系列聚合接口,还支持并发操作:parallelStream. 在爬虫开发 ...
- Java8 parallelStream与迭代器Iterator性能
定义一个测试类 public class TestParallelStream { private List<Integer> list; private int size; privat ...
- Java8 ParallelStream
ParallelStream 并行流就是一个把内容拆分成多个数据块,用不同线程分别处理每个数据块的流.对收集源调用parallelStream方法就能将集合转换为并行流. 并行流 并行流和顺序流转换 ...
- [源码解析] 当 Java Stream 遇见 Flink
[源码解析] 当 Java Stream 遇见 Flink 目录 [源码解析] 当 Java Stream 遇见 Flink 0x00 摘要 0x01 领域 1.1 Flink 1.2 Java St ...
- java8 Stream常用方法和特性浅析
有一个需求,每次需要将几万条数据从数据库中取出,并根据某些规则,逐条进行业务处理,原本准备批量进行for循环或者使用存储过程,但是for循环对于几万条数据来说效率较低:存储过程因为逻辑非常复杂,写起来 ...
- CAS、原子操作类的应用与浅析及Java8对其的优化
前几天刷朋友圈的时候,看到一段话:如果现在我是傻逼,那么我现在不管怎么努力,也还是傻逼,因为我现在的傻逼是由以前决定的,现在努力,是为了让以后的自己不再傻逼.话糙理不糙,如果妄想现在努力一下,马上就不 ...
- Java8新特性之重复注解(repeating annotations)浅析
Java8新特性之重复注解(repeating annotations)浅析 学习了:https://www.jb51.net/article/50827.htm
- java8的parallelStream提升数倍查询效率
业务场景 在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数,订单总金额,支出总金额等...这些业务通常都不是存在同一张表中,我们需要依次查询出来然后封装成所需要的对象返回给前端 ...
- java8中parallelStream提升数倍查询效率是怎样实现的,来看看这篇文章
作者:我恰芙蓉王 原文:https://www.cnblogs.com/-tang/p/13283216.html 业务场景 在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数, ...
随机推荐
- 教程:myeclipse在线安装svn插件
SVN 版本控制,相信开发过程中都很多有用到,今天在myeclipse 在线安装了SVN插件.下面是具体步骤,记录下,希望对有需要的朋友提供帮助. 要求: Myeclispe,电脑能连接互联网 步骤: ...
- flask 第六篇 flask内置的session
Flask中的Session非常的奇怪,他会将你的SessionID存放在客户端的Cookie中,使用起来也非常的奇怪 1. Flask 中 session 是需要 secret_key 的 from ...
- 卷boot仅剩余XX空间
参见: https://blog.csdn.net/hnzcdy/article/details/52381844
- GitHub:Alibaba
ylbtech-GitHub:Alibaba 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 1. https://github.com/alibaba 2. ...
- 使用谷歌提供的SwipeRefreshLayout下拉控件进行下拉刷新的实现数据的刷新
package com.loaderman.swiperefreshdemo; import android.os.Bundle; import android.os.Handler; import ...
- 溢出overflow: hidden
如果要防止内容把div容器或者表格撑大,可以在CSS中设置一.overflow: hidden; 表示如果内容超出容器大小,就把超出部分隐藏(相当于切掉)二.overflow: scroll; 这个表 ...
- Appium移动自动化测试(五)之应用操作
实验简介 对于APP测试来讲, 除了进行功能测试以外, 安装和卸载其实也是一个很重要的测试项. 试想, 如果用脚本来实现了, 可以事先判断该应用是否已安装, 根据判断结果来执行安装和卸载的动作, 循环 ...
- 由STL map调用clear后,内存不返还给操作系统的问题出发,探讨glibc malloc/free行为(转)
1. 问题 我们的程序有几十个线程,每个线程拥有一个std::map,每个线程都要向自己的std::map中插入大量的数据,但每个数据只有几十字节:当使用完std::map,调用map.clear() ...
- .NetCore接入Log4Net
首先接入NuGet包Log4Net 在项目中添加log4net.config文件 Log4Net的级别None>FATAL>ERROR>WARN>INFO>DEBUG&g ...
- spring boot configuration annotation processor not found in classpath
<dependency> <groupId> org.springframework.boot </groupId> <artifactId> spri ...