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 业务场景 在很多项目中,都有类似数据汇总的业务场景,查询今日注册会员数,在线会员数, ...
随机推荐
- 设置object的key为变量
https://blog.csdn.net/shu580231/article/details/81367271
- [CSP-S模拟测试]:物理课(数学)
题目传送门(内部题144) 输入格式 从$physics.in$读入数据. 第一行一个数$T$,代表有$T$组数据.接下来$T$行每行$4$个浮点数,分别为$\theta,v,d,g$,保留到小数点后 ...
- Java工程师成神之路(2018年最新版)
一.基础篇 JVM JVM内存结构 堆.栈.方法区.直接内存.堆和栈区别 Java内存模型 内存可见性.重排序.顺序一致性.volatile.锁.final 垃圾回收 内存分配策略.垃圾收集器(G1) ...
- javascript中“use strict”的好处和坏处
1.为什么使用严格模式? 消除javascript语法的一些不合理.不严谨之处,减少一些怪异行为: 消除代码运行的不安全之处,保证代码的运行: 提高编译效率,增加运行效率: 为未来新版本的javasc ...
- 浏览器缓存及vw和vh的使用
在浏览器缓存中不仅有 cookie 还有了别的选择 Storage 浏览器又分了两种缓存:sessionStorage localStorage localStorage 缓存:是一种永久的缓存,也就 ...
- thinkpad T480安装WIN7
本文转载自http://www.dnxtc.net/zixun/zhuangjijiaocheng/2018-12-01/3256.html 防止忘记 特别把资源集中一下 新买的笔记本预装的WIN10 ...
- ubuntu下如何使得普通用户能够启动wireshark?
一. 将dumpcap的用户组更改为wireshark sudo chgrp wireshark /usr/bin/dumpcap 二. 设置其他用户也具有与root一样的权限来执行dumpcap s ...
- Windows 下的SSH客户端
在日常Linux系统管理中,会使用SSH工具连接服务器,之所以SSH连接主要是为了安全,传统的telnet连接方式是以明文传输,很不安全,网络中如果又热窃听抓包,密码将要泄露.在众多SSH连接中,Pu ...
- Centos7.4 yum 安装MariaDB
#系统及版本选择:https://downloads.mariadb.org/mariadb/repositories/#mirror=tunavim /etc/yum.repos.d/MariaDB ...
- JMeter自带工具录制配置方法
打开Jmeter,看到左边显示一个空的测试计划,把该测试计划改名为TestPlan1.右键单击该测试计划,选择“添加”-“线程组”,添加一个线程组,改名为TestGroup1.为了录制浏览器的操作,需 ...