java8java parallelsorttream 循环中调用的方法如何实现多线程

都是原文或者翻译,都是作者亲身遇到的问题的解决方法或者看过的好文章翻译给大家分享。just share...
学会使用java8的parallelStream
java9都快出来了,现在java8已经很普及了。当然不排除有些公司由于太大积重难返,还在使用老版本。其实我现在公司挺多版本就还是用1.7。作为一个有追求的程序员哈哈,我一直在推动着1.8的使用。一开始就是喜
欢那简洁的lambda表达式,然后被他很多好用方便的特性的吸引。
parallelStream就是其中一个非常使用的特性。我总结了几个好处:
代码优雅,可以使用lambda表达式,原本几句代码现在一句可以搞定
运用多核特性(forkAndJoin)并行处理,大幅提高效率。
很明显好处就在第二点,不需要想破头脑优化,不需要扩容机器,一行简单的代码就让效率加倍。
不过,parallelStream虽好,但是也不宜滥用,有限制。需要考虑几点:
parallelStream 只是做到别浪费cpu,假如本身电脑cpu的负载很大,那还到处用parallel,那并不能起到作用
不要在多线程中使用parallelStream,原因同上类似,大家都抢着cpu是没有提升效果,反而还会加大线程切换开销
会带来不确定性,请确保每条处理无状态且没有关联
考虑nq模型:n可用的数据量,q针对每个数据元素执行的计算量乘积 N*Q 越大,就越有可能获得并行提速。N x Q & 10000 就会获得有效提升
没有更多推荐了,瓦力识别-答非所问stackoverflow.com/questions//why-does-parallel-stream-with-lambda-in-static-initializer-cause-a-deadlock赞同 添加评论分享收藏感谢收起java8新特性(六):Stream多线程并行数据处理
将一个顺序执行的流转变成一个并发的流只要调用 parallel()方法
public static long parallelSum(long n){
return Stream.iterate(1L, i -& i +1).limit(n).parallel().reduce(0L,Long::sum);
并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。
将一个并发流转成顺序的流只要调用sequential()方法
stream.parallel() .filter(...)
.sequential() .map(...) .parallel() .reduce();
这两个方法可以多次调用, 只有最后一个调用决定这个流是顺序的还是并发的。
并发流使用的默认线程数等于你机器的处理器核心数。
通过这个方法可以修改这个值,这是全局属性。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");
并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:
1. 如果不确定, 就自己测试。
2. 尽量使用基本类型的流
IntStream,
LongStream, and
DoubleStream
3. 有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst , 依赖元素顺序的操作在并发流中是极其消耗性能的 。findAny的性能就会好很多,应为不依赖顺序。
4. 考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间, N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高。
5. 数据量不大时使用并发流,性能得不到提升。
6.考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的。
流的数据源和可分解性
LinkedList
IntStream.range
Stream.iterate
7. 流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。
8. 考虑最终操作的性能:如果最终操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。
9.需要理解并发流实现机制:
fork/join 框架
fork/join框架是jdk1.7引入的,java8的stream多线程并非流的正是以这个框架为基础的,所以想要深入理解并发流就要学习fork/join框架。
fork/join框架的目的是以递归方式将可以并行的任务拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果。它是ExecutorService接口的一个实现,它把子任务分配线程池(ForkJoinPool)中的工作线程。要把任务提交到这个线程池,必须创建RecursiveTask&R&的一个子类,如果任务不返回结果则是RecursiveAction的子类。
fork/join框架流程示意图:
废话不多说,上代码:
import java.util.concurrent.ForkJoinPimport java.util.concurrent.ForkJoinTimport java.util.concurrent.RecursiveTimport java.util.stream.LongS/** * Created by sunjin on . * 继承RecursiveTask来创建可以用于分支/合并的框架任务 */public class ForkJoinSumCalculator extends RecursiveTask&Long& {
//要求和的数组
private final long[] numbers;
//子任务处理的数组开始和终止的位置
private final int start;
private final int end;
//不在将任务分解成子任务的阀值大小
public static final int THRESHOLD = <span style="color:#;
//用于创建组任务的构造函数
public ForkJoinSumCalculator(long[] numbers){
this(numbers, 0, numbers.length);
//用于递归创建子任务的构造函数
public ForkJoinSumCalculator(long[] numbers,int start,int end){
this.numbers =
this.start =
this.end =
//重写接口的方法
protected Long compute() {
//当前任务负责求和的部分的大小
int length = end - start;
//如果小于等于阀值就顺序执行计算结果
if(length &= THRESHOLD){
return computeSequentially();
//创建子任务来为数组的前一半求和
ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length/2);
//将子任务拆分出去,丢到ForkJoinPool线程池异步执行。
leftTask.fork();
//创建子任务来为数组的后一半求和
ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length/2, end);
//第二个任务直接使用当前线程计算而不再开启新的线程。
long rightResult = rightTask.compute();
//读取第一个子任务的结果,如果没有完成则等待。
long leftResult = leftTask.join();
//合并两个子任务的计算结果
return rightResult + leftR
//顺序执行计算的简单算法
private long computeSequentially(){
long sum = 0;
for(int i =start; i& end; i++){
sum += numbers[i];
//提供给外部使用的入口方法
public static long forkJoinSum(long n) {
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinTask&Long& task = new ForkJoinSumCalculator(numbers);
return new ForkJoinPool().invoke(task);
注意事项:
1. 调用join 方法要等到调用这个方法的线程的自己的任务完成之后。
2. 不要直接去调用ForkJoinPool的invoke方法 ,只需要调用RecursiveTask的fork或者compute。
3. 拆解任务时只需要调用一次fork执行其中一个子任务, 另一个子任务直接利用当前线程计算。应为fork方法只是在ForkJoinPool中计划一个任务。
4.任务拆分的粒度不宜太细,不否得不偿失。
由于各种因素,即便任务拆分是平均的,也不能保证所有子任务能同时执行结束, 大部分情况是某些子任务已经结束, 其他子任务还有很多, 在这个时候就会有很多资源空闲, 所以fork/join框架通过工作盗取机制来保证资源利用最大化, 让空闲的线程去偷取正在忙碌的线程的任务。
在没有任务线程中的任务存在一个队列当中, 线程每次会从头部获取一个任务执行,执行完了再从queue的头部获取一个任务,直到队列中的所有任务执行完,这个线程偷取别的线程队列中的任务时会从队列到尾部获取任务,并且执行,直到所有任务执行结束。
从这个角度分析,任务的粒度越小, 资源利用越充分。
工作盗取示意图
可拆分迭代器Spliterator
它和Iterator一样也是用于遍历数据源中的元素,但是他是为并行执行而设计的。 java8 所有数据结构都实现了 这个接口, 一般情况不需要自己写实现代码。但是了解它的实现方式会让你对并行流的工作原理有更深的了解。(未完待续)
没有更多推荐了,java8-Stream集合操作快速上手 - 简书
java8-Stream集合操作快速上手
Stream简介
为什么要使用Stream
实例数据源
Stream简介
Java 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。
stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。
只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
为什么要使用Stream
函数式编程带来的好处尤为明显。这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。易读的代码也易于维护、更可靠、更不容易出错。
实例数据源
public class Data {
private static List&PersonModel& list =
PersonModel wu = new PersonModel("wu qi", 18, "男");
PersonModel zhang = new PersonModel("zhang san", 19, "男");
PersonModel wang = new PersonModel("wang si", 20, "女");
PersonModel zhao = new PersonModel("zhao wu", 20, "男");
PersonModel chen = new PersonModel("chen liu", 21, "男");
list = Arrays.asList(wu, zhang, wang, zhao, chen);
public static List&PersonModel& getData() {
遍历数据并检查其中的元素时使用。
filter接受一个函数作为参数,该函数用Lambda表达式表示。
* 过滤所有的男性
public static void fiterSex(){
List&PersonModel& data = Data.getData();
List&PersonModel& temp=new ArrayList&&();
for (PersonModel person:data) {
if ("男".equals(person.getSex())){
temp.add(person);
System.out.println(temp);
List&PersonModel& collect = data
.filter(person -& "男".equals(person.getSex()))
.collect(toList());
System.out.println(collect);
* 过滤所有的男性 并且小于20岁
public static void fiterSexAndAge(){
List&PersonModel& data = Data.getData();
List&PersonModel& temp=new ArrayList&&();
for (PersonModel person:data) {
if ("男".equals(person.getSex())&&person.getAge()&20){
temp.add(person);
List&PersonModel& collect = data
.filter(person -& {
if ("男".equals(person.getSex())&&person.getAge()&20){
.collect(toList());
List&PersonModel& collect1 = data
.filter(person -& ("男".equals(person.getSex())&&person.getAge()&20))
.collect(toList());
map生成的是个一对一映射,for的作用
而且很简单
* 取出所有的用户名字
public static void getUserNameList(){
List&PersonModel& data = Data.getData();
List&String& list=new ArrayList&&();
for (PersonModel persion:data) {
list.add(persion.getName());
System.out.println(list);
List&String& collect = data.stream().map(person -& person.getName()).collect(toList());
System.out.println(collect);
List&String& collect1 = data.stream().map(PersonModel::getName).collect(toList());
System.out.println(collect1);
List&String& collect2 = data.stream().map(person -& {
System.out.println(person.getName());
return person.getName();
}).collect(toList());
顾名思义,跟map差不多,更深层次的操作
但还是有区别的
map和flat返回值不同
Map 每个输入元素,都按照规则转换成为另外一个元素。
还有一些场景,是一对多映射关系的,这时需要 flatMap。
Flatmap一对多
map和flatMap的方法声明是不一样的
&r& Stream&r&
map(Function mapper);
&r& Stream&r& flatMap(Function& mapper);
map和flatMap的区别:我个人认为,flatMap的可以处理更深层次的数据,入参为多个list,结果可以返回为一个list,而map是一对一的,入参是多个list,结果返回必须是多个list。通俗的说,如果入参都是对象,那么flatMap可以操作对象里面的对象,而map只能操作第一层。
public static void flatMapString() {
List&PersonModel& data = Data.getData();
//返回类型不一样
List&String& collect = data.stream()
.flatMap(person -& Arrays.stream(person.getName().split(" "))).collect(toList());
List&Stream&String&& collect1 = data.stream()
.map(person -& Arrays.stream(person.getName().split(" "))).collect(toList());
//用map实现
List&String& collect2 = data.stream()
.map(person -& person.getName().split(" "))
.flatMap(Arrays::stream).collect(toList());
//另一种方式
List&String& collect3 = data.stream()
.map(person -& person.getName().split(" "))
.flatMap(str -& Arrays.asList(str).stream()).collect(toList());
感觉类似递归
数字(字符串)累加
个人没咋用过
public static void reduceTest(){
//累加,初始化值是 10
Integer reduce = Stream.of(1, 2, 3, 4)
.reduce(10, (count, item) -&{
System.out.println("count:"+count);
System.out.println("item:"+item);
return count +
System.out.println(reduce);
Integer reduce1 = Stream.of(1, 2, 3, 4)
.reduce(0, (x, y) -& x + y);
System.out.println(reduce1);
String reduce2 = Stream.of("1", "2", "3")
.reduce("0", (x, y) -& (x + "," + y));
System.out.println(reduce2);
collect在流中生成列表,map,等常用的数据结构
public static void toListTest(){
List&PersonModel& data = Data.getData();
List&String& collect = data.stream()
.map(PersonModel::getName)
.collect(Collectors.toList());
public static void toSetTest(){
List&PersonModel& data = Data.getData();
Set&String& collect = data.stream()
.map(PersonModel::getName)
.collect(Collectors.toSet());
public static void toMapTest(){
List&PersonModel& data = Data.getData();
Map&String, Integer& collect = data.stream()
Collectors.toMap(PersonModel::getName, PersonModel::getAge)
data.stream()
.collect(Collectors.toMap(per-&per.getName(), value-&{
return value+"1";
* 指定类型
public static void toTreeSetTest(){
List&PersonModel& data = Data.getData();
TreeSet&PersonModel& collect = data.stream()
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(collect);
public static void toGroupTest(){
List&PersonModel& data = Data.getData();
Map&Boolean, List&PersonModel&& collect = data.stream()
.collect(Collectors.groupingBy(per -& "男".equals(per.getSex())));
System.out.println(collect);
public static void toJoiningTest(){
List&PersonModel& data = Data.getData();
String collect = data.stream()
.map(personModel -& personModel.getName())
.collect(Collectors.joining(",", "{", "}"));
System.out.println(collect);
public static void reduce(){
List&String& collect = Stream.of("1", "2", "3").collect(
Collectors.reducing(new ArrayList&String&(), x -& Arrays.asList(x), (y, z) -& {
y.addAll(z);
System.out.println(collect);
Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。
人们对原有的 null 值有很多抱怨,甚至连发明这一概念的Tony Hoare也是如此,他曾说这是自己的一个“价值连城的错误”
用处很广,不光在lambda中,哪都能用
Optional.of(T),T为非空,否则初始化报错
Optional.ofNullable(T),T为任意,可以为空
isPresent(),相当于 !=null
ifPresent(T), T可以是一段lambda表达式 ,或者其他代码,非空则执行
public static void main(String[] args) {
PersonModel personModel=new PersonModel();
//对象为空则打出 -
Optional&Object& o = Optional.of(personModel);
System.out.println(o.isPresent()?o.get():"-");
//名称为空则打出 -
Optional&String& name = Optional.ofNullable(personModel.getName());
System.out.println(name.isPresent()?name.get():"-");
//如果不为空,则打出xxx
Optional.ofNullable("test").ifPresent(na-&{
System.out.println(na+"ifPresent");
//如果空,则返回指定字符串
System.out.println(Optional.ofNullable(null).orElse("-"));
System.out.println(Optional.ofNullable("1").orElse("-"));
//如果空,则返回 指定方法,或者代码
System.out.println(Optional.ofNullable(null).orElseGet(()-&{
return "hahah";
System.out.println(Optional.ofNullable("1").orElseGet(()-&{
return "hahah";
//如果空,则可以抛出异常
System.out.println(Optional.ofNullable("1").orElseThrow(()-&{
throw new RuntimeException("ss");
Objects.requireNonNull(null,"is null");
//利用 Optional 进行多级判断
EarthModel earthModel1 = new EarthModel();
if (earthModel1!=null){
if (earthModel1.getTea()!=null){
Optional.ofNullable(earthModel1)
.map(EarthModel::getTea)
.map(TeaModel::getType)
.isPresent();
Optional&EarthModel& earthModel = Optional.ofNullable(new EarthModel());
Optional&List&PersonModel&& personModels = earthModel.map(EarthModel::getPersonModels);
Optional&Stream&String&& stringStream = personModels.map(per -& per.stream().map(PersonModel::getName));
//判断对象中的list
Optional.ofNullable(new EarthModel())
.map(EarthModel::getPersonModels)
.map(pers-&pers
.map(PersonModel::getName)
.collect(toList()))
.ifPresent(per-& System.out.println(per));
List&PersonModel& models=Data.getData();
Optional.ofNullable(models)
.map(per -& per
.map(PersonModel::getName)
.collect(toList()))
.ifPresent(per-& System.out.println(per));
stream替换成parallelStream或 parallel
输入流的大小并不是决定并行化是否会带来速度提升的唯一因素,性能还会受到编写代码的方式和核的数量的影响
影响性能的五要素是:数据大小、源数据结构、值是否装箱、可用的CPU核数量,以及处理每个元素所花的时间
//根据数字的大小,有不同的结果
private static int size=;
public static void main(String[] args) {
System.out.println("-----------List-----------");
testList();
System.out.println("-----------Set-----------");
testSet();
* 测试list
public static void testList(){
List&Integer& list = new ArrayList&&(size);
for (Integer i = 0; i & i++) {
list.add(new Integer(i));
List&Integer& temp1 = new ArrayList&&(size);
long start=System.currentTimeMillis();
for (Integer i: list) {
temp1.add(i);
System.out.println(+System.currentTimeMillis()-start);
long start1=System.currentTimeMillis();
list.stream().collect(Collectors.toList());
System.out.println(System.currentTimeMillis()-start1);
long start2=System.currentTimeMillis();
list.parallelStream().collect(Collectors.toList());
System.out.println(System.currentTimeMillis()-start2);
public static void testSet(){
List&Integer& list = new ArrayList&&(size);
for (Integer i = 0; i & i++) {
list.add(new Integer(i));
Set&Integer& temp1 = new HashSet&&(size);
long start=System.currentTimeMillis();
for (Integer i: list) {
temp1.add(i);
System.out.println(+System.currentTimeMillis()-start);
long start1=System.currentTimeMillis();
list.stream().collect(Collectors.toSet());
System.out.println(System.currentTimeMillis()-start1);
long start2=System.currentTimeMillis();
list.parallelStream().collect(Collectors.toSet());
System.out.println(System.currentTimeMillis()-start2);
list.map.fiter.map.xx 为链式调用,最终调用collect(xx)返回结果
分惰性求值和及早求值
判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。
通过peek可以查看每个值,同时能继续操作流
private static void peekTest() {
List&PersonModel& data = Data.getData();
//peek打印出遍历的每个per
data.stream().map(per-&per.getName()).peek(p-&{
System.out.println(p);
}).collect(toList());
请各路大佬指教
三天不读书,感觉像只猪
Jav8中,在核心类库中引入了新的概念,流(Stream)。流使得程序媛们得以站在更高的抽象层次上对集合进行操作。今天,居士将主要介绍Steam类中对应集合上操作的几个重要的方法。 1、 Steam举例 对使用Java的程序媛们,当需要处理集合里的每一个数据时,通常是使用迭...
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式...
了解Stream
Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream API,针对常见的集合数据处理,Stream API 提供了一种高效且易于使用的数据处理方式。 什么是Stream 基本概念
流(Stream)用于操作数据...
Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式编程范式的基石。 Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算。这里的关键点在于,Stream允许并提倡并行处理一个S...
Int Double Long 设置特定的stream类型, 提高性能,增加特定的函数 无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。 为函数式编程而生。对stream的任何修改都不会修改背后...
笑话: 1 “你那里空气质量咋样?”“还行,一年只有7天雾霾”“这么好,哪年啊?”“2017年。” 2 远古时代,几个原始人刚刚学会钻木取火,夜晚他们升起一簇篝火,围在一起凝视着篝火,感觉很奇妙,突然有一个人打破沉默说:“可惜火锅还要几十万年之后才能发明出来啊! 3 女:你...
问题描述 如下图,执行ionic serve依赖node-sass找不到node-sass下载失败 如下图,执行cnpm i报错 错误原因1:网络问题下载node-sassa失败 解决方法1 尝试使用移动网络下载 解决方法2 去这里下载win32-x64-47_bindin...
今年的七夕,依然是我一个人,然后看了一集《我们相爱吧》,就特别希望有爱情,单身久了就不知道爱情是什么样子的了?
有木有谁?跟我一起恋爱吧……
跟黑夜对抗着 昨天今天明天 像怪异的风 旋转着合势狂涌过来 恐惧攫住我 可我却在这无限延伸的黑暗里 如鲠在喉呼喊不出声音 慢慢闭上眼 我像搁浅在沙滩上的鱼儿 呼吸微弱 可是 就在一刹那 我想起你含笑的眼眸 像黑夜里的如豆的灯光 我在黑夜里 我是迷路的孩子 亲爱的 你会不会再...Java8&parallelStream&API&线程池耗光
的语法精简干练。但也存在着一些坑比如我查了两天才查出来的线程池耗光问题:
collection.parallelStream.foreach()
如果集合数目大,会申请大量线程,导致线程池耗光,IO卡主,无法提供对外服务。
解决办法,去掉parallelStream,在适当的地方用可以提高运行效率,但应用不当时带来难以查找的问题。最常见的不适合用parallelStream的地方之一就是web项目或者对外提供高qps的服务的地方。
用parallel运行或许很好,也或许不是,这取决于你如何用它的特点。
2. parallel stream
或许让你的程序更快,或者更慢。
考虑用流作为一种较低成本获取并行处理方式,避免开发者去考虑究竟发生了什么。流与并行处理并没有直接关系
4. 上边的问题基本都基于不理解并行处理与并发处理并不是同一个事情。并且关于自动并行化(automatic
parallelization)的java8的大部分例子事实上是并发处理的例子。
5. 考虑映射,过滤和其他的操作作为内部迭代(internal
iteration)是无稽之谈(尽管这并不是java8的问题,但与我们的用法有关)
&parallel stream
与sequential one相比有更高的开销,协调线程花费大量时间。我会默认选择sequential streams
,在以下情况下考虑parallel stream
有大量items去处理(或者处理每个item需要花费一些时间,并且是可并行的话)
2. 性能问题是首要考虑的
我没有运行这程序在一个多线程环境中(例如在web容器,如果我已经有许多请求并行处理,在每个请求增加一个额外的并行机制,带来的坏处要大于带来的好处)
API的设计让我们用抽象的方式写计算程序,而不关心内部执行,使串行和并行切换变得很容易。然而,它很容易去书写,但并不意味着经常是一个好的方式。
&首先:请注意,并行性并不能带来什么好处除了更多的核可用的情况下能更快执行。一个并行执行一般工作量更大,因为需要额外资源去解决这个问题,并且需要调度与协调子任务。希望你可以更快得到答案通过分解工作来跨多个处理器;是否真的更快要取决于以下几件事情,包括1.数据集的大小,2.对于每个元素计算量,3.计算有序性,4.可用处理器数目,5.竞争处理器其他任务的数目。
另外:并行机制经常在计算中注入非确定性机制。有时后无所谓,也可以通过约束涉及到的操作来减轻影响。事实上,有时并行机制将加速你的计算,有时会减慢计算,最好的方式是首先用sequential执行来发开,然后如果你确切的知道(A)确实能从中受益,(B)并行机制将提高你的性能,再用parallelism.
A是一个商业问题,并不是技术问题,如果你是个性能专家,你通常能看完代码后决定B,但是聪明的方式是去测试一下。
最简单的并行性能模型是“NQ“模型,N是元素个数,Q是每个元素的计算量。通常,在你开始从性能上受益之前你需要你的NQ达到某个阈值。对于一个低Q的问题,比如从1到N加起来,你先整体看下N=1000,和N=10000的盈亏平衡。对于高Q的问题,盈亏阈值也低。
但是现实是非常复杂的。首先识别串行处理确实花费你一些东西,然后测试是否并行机能够带来帮助,之后再用并行。
So what about parallel processing?
One most advertised functionality of streams is that they allow
automatic parallelization of processing. And one can find the
amazing demonstrations on the web, mainly based of the same example
of a program contacting a server to get the values corresponding to
a list of stocks and finding the highest one not exceeding a given
limit value. Such an example may show an increase of speed of
400&% and more.
But this example as little to do with parallel processing. It is an
example of concurrent processing, which means that the increase of
speed will be observed also on a single processor computer. This is
because the main part of each “parallel” task is waiting. Parallel
processing is about running at the same time tasks that do no wait,
such as intensive calculations.
Automatic parallelization will generally not give the expected
result for at least two reasons:
increase of speed is highly dependent upon the kind of task and the
parallelization strategy. And over all things, the best strategy is
dependent upon the type of task.
increase of speed in highly dependent upon the environment. In some
environments, it is easy to obtain a decrease of speed by
parallelizing.
Whatever the kind of tasks to parallelize, the strategy applied by
parallel streams will be the same, unless you devise this strategy
yourself, which will remove much of the interest of parallel
streams. Parallelization requires:
threads to execute the subtasks,
the initial task into subtasks,
Distributing subtasks to threads,
the results.
Without entering the details, all this implies some overhead. It
will show amazing results when:
tasks imply blocking for a long time, such as accessing a remote
service, or
not many threads running at the same time, and in particular no
other parallel stream.
If all subtasks imply intense calculation, the potential gain is
limited by the number of available processors.
Java&8 will by default use as many threads as they
are processors on the computer, so, for intensive tasks, the result
is highly dependent upon what other threads may be doing at the
same time. Of course, if each subtask is essentially waiting, the
gain may appear to be huge.
The worst case is if the application runs in a server or a
container alongside other applications, and subtasks do not imply
waiting. In such a case, (for example running in a J2EE server),
parallel streams will often be slower that serial ones. Imagine a
server serving hundreds of requests each second. There are great
chances that several streams might be evaluated at the same time,
so the work is already parallelized. A new layer of parallelization
at the business level will most probably make things slower.
Worst: there are great chances that the business applications will
see a speed increase in the development environment and a decrease
in production. And that is the worst possible situation.
Edit: for a better understanding of why parallel streams in Java 8
(and the Fork/Join pool in Java 7) are broken, refer to these
excellent articles by Edward Harned:
What streams are good for
Stream are a useful tool because they allow lazy evaluation. This
is very important in several aspect:
allow functional programming style using bindings.
allow for better performance by removing iteration. Iteration
occurs with evaluation. With streams, we can bind dozens of
functions&without iterating.
allow easy parallelization for task including long waits.
may be infinite (since they are lazy). Functions may be bound to
infinite streams without problem. Upon evaluation, there must be
some way to make them finite. This is often done through a short
circuiting operation.
What streams are not good for
Streams should be used with high caution when processing intensive
computation tasks. In particular, by default, all streams will use
the same&ForkJoinPool,
configured to use as many threads as there are cores in the
computer on which the program is running.
If evaluation of one parallel stream results in a very long running
task, this may be split into as many long running sub-tasks that
will be distributed to each thread in the pool. From there, no
other parallel stream can be processed because all threads will be
occupied. So, for computation intensive stream evaluation, one
should always use a specific&ForkJoinPool&in
order not to block other streams.
To do this, one may create a&Callable&from
the stream and submit it to the pool:
List&&/span&SomeClass& list = // A list of objects
Stream&&/span&SomeClass& stream = list.parallelStream().map(this::veryLongProcessing);
Callable&&/span&List&&/span&Integer&& task = () -& stream.collect(toList());
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
List&&/span&SomeClass& newList = forkJoinPool.submit(task).get()
This way, other parallel streams (using their
own&ForkJoinPool)
will not be blocked by this one. In other words, we would need a
pool of&ForkJoinPool&in
order to avoid this problem.
If a program is to be run inside a container, one must be very
careful when using parallel streams. Never use the default pool in
such a situation unless you know for sure that the container can
handle it. In a Java&EE container, do not use
parallel streams.
Previous articles
http://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
http://ifeve.com/stream/
http://www.cnblogs.com/treerain/p/java8_stream.html
&http://stackoverflow.com/questions//should-i-always-use-a-parallel-stream-when-possible&
https://dzone.com/articles/whats-wrong-java-8-part-iii
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。}

我要回帖

更多关于 java8 parallelstream 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信