译注:文本有所精简和意译 原文链接 : Java Parallel Streams Are Bad for Your Health!
原作者:OLEG SHELAJEV 翻译:Hason 转载请保留相关信息

Java8 提供了三个我们渴望的重要的功能:Lambdas 、 Stream API、以及接口的默认方法。不过我们很容易滥用它们甚至破坏自己的代码。

今天我们来看看Stream api,尤其是 parallel streams。这篇文章概述了其中的陷阱。

但是首先让我们看看Stream api备受称赞的原因——并行执行。它通过默认的ForkJoinPool,可能提高你的多线程任务的速度。

Parallel Streams 的陷阱

以下是一个使用 parallel streams 完美特性的经典例子。在这个例子中,我们想同时查询多个搜索引擎并且获得第一个返回的结果。

 public static String query(String question) {
List<String> engines = new ArrayList<String>() {{
add("http://www.google.com/?q=");
add("http://duckduckgo.com/?q=");
add("http://www.bing.com/search?q=");
}};
// get element as soon as it is available
Optional<String> result = engines.stream().parallel().map((base) -> {
String url = base + question;
// open connection and fetch the result
return WS.url(url).get();
}).findAny();
return result.get();
}

是不是很棒?但是让我们细思深挖一下背后发生了什么。Parallel streams 被父线程执行并且使用JVM默认的 fork join pool: ForkJoinPool.common().(关于fork join 上面有链接)

然而,这里需要注意的一个重要方面是——查询搜索引擎是一个阻塞操作。所以在某时刻所有线程都会调用get()方法并且在那里等待结果返回。

等等,这是我们一开始想要的吗?我们是在同一时间等待所有的结果,而不是遍历这个列表按顺序等待每个回答。

然而,由于ForkJoinPool workders的存在,这样平行的等待相对于使用主线程的等待会产生的一种副作用。现在ForkJoin pool 的实现是:它并不会因为产生了新的workers而抵消掉阻塞的workers。那么在某个时间所有ForkJoinPool.common()的线程都会被用光。

也就是说,下一次你调用这个查询方法,就可能会在一个时间与其他的parallel stream同时运行,而导致第二个任务的性能大大受损。

不过也不要急着去吐槽ForkJoinPool的实现。在不同的情况下你可以给它一个ManagedBlocker实例并且确保它知道在一个阻塞调用中应该什么时候去移除掉卡住的workers。

现在有意思的一点是,在一个parallel stream处理中并不一定是阻塞调用会拖延程序的性能。任何被用于映射在一个集合上的长时间运行的函数都会产生同样的问题。

看下面这个例子

 long a = IntStream.range(0, 100).mapToLong(x -> {
for (int i = 0; i < 100_000_000; i++) {
System.out.println("X:" + i);
}
return x;
}).sum();

这段代码同上面那个网络访问的代码遇到了相同的问题。每个lambda的执行并不是瞬间完成的,并且在执行过程中程序中的其他部分将无法访问这些workers。

这意味着任何依赖parallel streams的程序在什么别的东西占用着common ForkJoinPool时将会变得不可预知并且暗藏危机。

那又怎么样?我不还是我程序的主人

确实,如果你正在写一个其他地方都是单线程的程序并且准确地知道什么时候你应该要使用parallel streams,这样的话你可能会觉得这个问题有一点肤浅。然而,我们很多人是在处理web应用、各种不同的框架以及重量级应用服务。

一个服务器是怎样被设计成一个可以支持多种独立应用的主机的?谁知道呢,给你一个可以并行的却不能控制输入的parallel stream?(offer you a predictable parallel stream performance if it doesn’t control the inputs)

一种方式是限制ForkJoinPool提供的并行数。可以通过使用-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 来限制线程池的大小为1。不再从并行化中得到好处可以杜绝错误的使用它。

另一种方式就是,一个被称为工作区的可以让ForkJoinPool平行放置的 parallelStream() 实现。不幸的是现在的JDK还没有实现。

总结

Parallel streams 是无法预测的,而且想要正确地使用它有些棘手。几乎任何parallel streams的使用都会影响程序中无关部分的性能,而且是一种无法预测的方式。我毫不怀疑有人能够设法去正确有效地使用它们。但是在打出stream.parallel()在我的代码里之前我仍然会仔细思考并且再三地审阅包含它的所有代码。

Java8中 Parallel Streams 的陷阱 [译]的更多相关文章

  1. Java8中的流操作-基本使用&性能测试

    为获得更好的阅读体验,请访问原文:传送门 一.流(Stream)简介 流是 Java8 中 API 的新成员,它允许你以声明式的方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).这有点儿 ...

  2. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

  3. Java8中的Stream API

    本篇文章继续介绍Java 8的另一个新特性——Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...

  4. java8中map的meger方法的使用

    java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...

  5. java8中CAS的增强

    注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整.转载请注明出处! 一.java8中CAS的增强 前些天,我偶然地将之前写的用来测试AtomicInteger和synchronize ...

  6. java8中的map和reduce

    java8中的map和reduce 标签: java8函数式mapreduce 2014-06-19 19:14 10330人阅读 评论(4) 收藏 举报  分类: java(47)  FP(2)  ...

  7. java8 中的时间和数据的变化

    java8除了lambda表达式之外还对时间和数组这两块常用API做想应调整, Stream 有几个常用函数: store 排序 (a,b)-> a.compareTo(b)  排出来的结果是正 ...

  8. 理解 PHP 中的 Streams

    Streams 是PHP提供的一个强有力的工具,我们常常在不经意会使用到它,如果善加利用将大大提高PHP的生产力. 驾驭Streams的强大力量后,应用程序将提升到一个新的高度. 下面是PHP手册中对 ...

  9. Java8中Lambda表达式的10个例子

    Java8中Lambda表达式的10个例子 例1 用Lambda表达式实现Runnable接口 //Before Java 8: new Thread(new Runnable() { @Overri ...

随机推荐

  1. 关于IE6、IE7、IE8实现盒子阴影shadow的几个注意点

    通常,我们实现盒阴影都是通过这段代码来实现的 -moz-box-shadow: 3px 3px 4px #000; -webkit-box-shadow: 3px 3px 4px #000; box- ...

  2. 5分钟了解MySQL5.7的Online DDL雷区

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://suifu.blog.51cto.com/9167728/1855872 Part ...

  3. AngularJS 讲解五, Factory ,Service , Provider

    一. 首先说一下,为什么要引入Factory,Service和Provider这三个Service层. 1.因为我们不应该在controller层写入大量的业务逻辑和持久化数据,controller层 ...

  4. JS 继承(类式 与 原型式)

    1. /* -- 类式继承 -- *///先声明一个超类 function Person(name) { this.name = name;}//给这个超类的原型对象上添加方法 getName Per ...

  5. 《疯狂Java讲义》(七)---- 方法

    一 方法的参数传递机制 Java方法的参数传递方式只有一种:值传递.就是将实际参数值的副本传入方法内,而参数本身不会受到任何影响. eg. 基本类型的值传递 public class Primitiv ...

  6. c#使用DotNetZip封装类操作zip文件(创建/读取/更新)实例

    DotnetZip是一个开源类库,支持.NET的任何语言,可很方便的创建,读取,和更新zip文件.而且还可以使用在.NETCompact Framework中.  下载地址在这里:http://dot ...

  7. jq操作radio,设置选中、获取选中值

    <label><inputtype="radio"name="sex"value="1">男</label&g ...

  8. ASP.NET - 自定义控件处理页面事件(控件与页面数据交互)的方法

    //用委托的方法实现 //控件代码 public delegate void DelegateFunction( string sPageTitle ); private DelegateFuncti ...

  9. SSM整合XML版(Maven Project)

    一.使用IntelliJ IDEA 创建Maven管理的JAVA Web项目 ①新建项目,选择Maven Module 在本地磁盘用户的..m2目录下生成settings文件: Maven工程搭建完毕 ...

  10. android: activity之间切换的抽屉效果

    之前一直用的是向左平移和向右平移的切换动画,看到别的APP那个抽屉效果,自己也弄了一个!感谢给我提供帮助的大神们! 将退出动画的参数设置为0时,进入动画则设置为向左平移,就实现了抽屉效果! 进入的动画 ...