Java 8: CompletableFuture vs Parallel Stream
来源:互联网 发布:淘宝护肤品有真的吗 编辑:程序博客网 时间:2024/06/05 11:06
Java 8: CompletableFuture vs Parallel Stream
This post shows how Java 8's CompletableFuture
compares with parallel streams when peforming asynchronous computations.
We will use the following class to model a long-running task:
class MyTask { private final int duration; public MyTask(int duration) { this.duration = duration; } public int calculate() { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(duration * 1000); } catch (final InterruptedException e) { throw new RuntimeException(e); } return duration; }}
Let's create ten tasks, each with a duration of 1 second:
List<MyTask> tasks = IntStream.range(0, 10) .mapToObj(i -> new MyTask(1)) .collect(toList());
How can we calculate the list of tasks efficiently?
Approach 1: Sequentially
Your first thought might be to calculate the tasks sequentially, as follows:
public static void runSequentially(List<MyTask> tasks) { long start = System.nanoTime(); List<Integer> result = tasks.stream() .map(MyTask::calculate) .collect(toList()); long duration = (System.nanoTime() - start) / 1_000_000; System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration); System.out.println(result);}
As you might expect, this takes 10 seconds to run, because each task is run one after the other on the main
thread.
Approach 2: Using a parallel stream
A quick improvement is to convert your code to use a parallel stream, as shown below:
public static void useParallelStream(List<MyTask> tasks) { long start = System.nanoTime(); List<Integer> result = tasks.parallelStream() .map(MyTask::calculate) .collect(toList()); long duration = (System.nanoTime() - start) / 1_000_000; System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration); System.out.println(result);}
The output is
mainForkJoinPool.commonPool-worker-1ForkJoinPool.commonPool-worker-3ForkJoinPool.commonPool-worker-2ForkJoinPool.commonPool-worker-3ForkJoinPool.commonPool-worker-2mainForkJoinPool.commonPool-worker-1ForkJoinPool.commonPool-worker-1mainProcessed 10 tasks in 3043 millis
This time it took 3 seconds because 4 tasks were run in parallel (using three threads from the ForkJoinPool
, plus the main
thread).
Approach 3: Using CompletableFutures
Let's see if CompletableFuture
s perform any better:
public static void useCompletableFuture(List<MyTask> tasks) { long start = System.nanoTime(); List<CompletableFuture<Integer>> futures = tasks.stream() .map(t -> CompletableFuture.supplyAsync(() -> t.calculate())) .collect(Collectors.toList()); List<Integer> result = futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); long duration = (System.nanoTime() - start) / 1_000_000; System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration); System.out.println(result);}
In the code above, we first obtain a list of CompletableFuture
s and then invoke the join
method on each future to wait for them to complete one by one. Note that join
is the same as get
, with the only difference being that the former doesn't throw any checked exception, so it's more convenient in a lambda expression.
Also, you must use two separate stream pipelines, as opposed to putting the two map operations after each other, because intermediate stream operations are lazy and you would have ended up processing your tasks sequentially! That's why you first need to collect your CompletableFuture
s in a list to allow them to start before waiting for their completion.
The output is
ForkJoinPool.commonPool-worker-1ForkJoinPool.commonPool-worker-2ForkJoinPool.commonPool-worker-3ForkJoinPool.commonPool-worker-1ForkJoinPool.commonPool-worker-2ForkJoinPool.commonPool-worker-3ForkJoinPool.commonPool-worker-1ForkJoinPool.commonPool-worker-2ForkJoinPool.commonPool-worker-3ForkJoinPool.commonPool-worker-1Processed 10 tasks in 4010 millis
It took 4 seconds to process 10 tasks. You will notice that only 3 ForkJoinPool threads were used and that, unlike the parallel stream, the main
thread was not used.
Approach 4: Using CompletableFutures with a custom Executor
One of the advantages of CompletableFuture
s over parallel streams is that they allow you to specify a different Executor
to submit their tasks to. This means that you can choose a more suitable number of threads based on your application. Since my example is not very CPU-intensive, I can choose to increase the number of threads to be greater than Runtime.getRuntime().getAvailableProcessors()
, as shown below:
public static void useCompletableFutureWithExecutor(List<MyTask> tasks) { long start = System.nanoTime(); ExecutorService executor = Executors.newFixedThreadPool(Math.min(tasks.size(), 10)); List<CompletableFuture<Integer>> futures = tasks.stream() .map(t -> CompletableFuture.supplyAsync(() -> t.calculate(), executor)) .collect(Collectors.toList()); List<Integer> result = futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); long duration = (System.nanoTime() - start) / 1_000_000; System.out.printf("Processed %d tasks in %d millis\n", tasks.size(), duration); System.out.println(result); executor.shutdown();}
The output is
pool-1-thread-2pool-1-thread-4pool-1-thread-3pool-1-thread-1pool-1-thread-5pool-1-thread-6pool-1-thread-7pool-1-thread-8pool-1-thread-9pool-1-thread-10Processed 10 tasks in 1009 millis
After this improvement, it now takes only 1 second to process 10 tasks.
As you can see, CompletableFuture
s provide more control over the size of the thread pool and should be used if your tasks involve I/O. However, if you're doing CPU-intensive operations, there's no point in having more threads than processors, so go for a parallel stream, as it is easier to use.
From:http://fahdshariff.blogspot.com/2016/06/java-8-completablefuture-vs-parallel.html
- Java 8: CompletableFuture vs Parallel Stream
- Java你应该懂点多线程1-Stream、Executor、CompletableFuture
- Java 8:CompletableFuture终极指南
- Java 8 CompletableFuture 浅入
- Java CompletableFuture
- Java 8 lambda stream forEach parallel 等循环与Java 7 for each 循环耗时测试
- Java 8 vs. Scala(二):Stream vs. Collection
- Java 8: Writing asynchronous code with CompletableFuture
- Java CompletableFuture 详解
- Java CompletableFuture 详解
- Java里面CompletableFuture详解
- Garbage Collectors – Serial vs. Parallel vs. CMS vs. G1 (and what’s new in Java 8)
- jdk1.8 CompletableFuture尝试
- CompletableFuture
- CompletableFuture
- CompletableFuture
- CompletableFuture
- Java Stream(8)(Stream完结)
- Linux后台运行 nohup command >/dev/null 2>&1 &
- 文件读写 iflie对象(读操作) / ofile对象(写操作)
- AdminEAP框架简介
- 数据结构与算法分析之平衡二叉树的建立
- docker系列二: docker安装Redis
- Java 8: CompletableFuture vs Parallel Stream
- 数据结构之平衡二叉树
- 原型模式
- Java 内存泄漏
- solr 环境搭建
- 1044. Shopping in Mars (25)
- 数据结构之红黑树
- 网络编程
- Linux Kernel ROP