加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Java8 parallelStream API 线程池耗光

(2015-12-22 22:55:50)
标签:

java8

foreach

parallestream

线程池

卡主

分类: study
java8 的语法精简干练。但也存在着一些坑比如我查了两天才查出来的线程池耗光问题:

collection.parallelStream.foreach()
如果集合数目大,会申请大量线程,导致线程池耗光,IO卡主,无法提供对外服务。
解决办法,去掉parallelStream,在适当的地方用可以提高运行效率,但应用不当时带来难以查找的问题。最常见的不适合用parallelStream的地方之一就是web项目或者对外提供高qps的服务的地方。

1. 用parallel运行或许很好,也或许不是,这取决于你如何用它的特点。
2. parallel stream 或许让你的程序更快,或者更慢。
3. 考虑用流作为一种较低成本获取并行处理方式,避免开发者去考虑究竟发生了什么。流与并行处理并没有直接关系
4. 上边的问题基本都基于不理解并行处理与并发处理并不是同一个事情。并且关于自动并行化(automatic parallelization)的java8的大部分例子事实上是并发处理的例子。
5. 考虑映射,过滤和其他的操作作为内部迭代(internal iteration)是无稽之谈(尽管这并不是java8的问题,但与我们的用法有关)

 parallel stream 与sequential one相比有更高的开销,协调线程花费大量时间。我会默认选择sequential streams ,在以下情况下考虑parallel stream
1. 有大量items去处理(或者处理每个item需要花费一些时间,并且是可并行的话)
2. 性能问题是首要考虑的
3. 我没有运行这程序在一个多线程环境中(例如在web容器,如果我已经有许多请求并行处理,在每个请求增加一个额外的并行机制,带来的坏处要大于带来的好处)

       Stream 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:

  1. The 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.
  2. The 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:

  • A pool of threads to execute the subtasks,
  • Dividing the initial task into subtasks,
  • Distributing subtasks to threads,
  • Collating the results.

Without entering the details, all this implies some overhead. It will show amazing results when:

  • Some tasks imply blocking for a long time, such as accessing a remote service, or
  • There are 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:

  • They allow functional programming style using bindings.
  • They allow for better performance by removing iteration. Iteration occurs with evaluation. With streams, we can bind dozens of functions without iterating.
  • They allow easy parallelization for task including long waits.
  • Streams 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

What's Wrong with Java 8, Part I: Currying vs Closures

What's Wrong in Java 8, Part II: Functions & Primitives


参考:
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/20375176/should-i-always-use-a-parallel-stream-when-possible 

      https://dzone.com/articles/whats-wrong-java-8-part-iii


推广:51说图

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有