开启线程执行任务,不管是使用Runnable(无返回值不支持上报异常)还是Callable(有返回值支持上报异常)接口,都可以轻松实现。那么如果是开启线程池并需要获取结果归集的情况下,如何实现,以及优劣,老司机直接看总结即可。

任务执行完,结果归集时,几种方式:

1.Futrue

原理:

如下图,Future接口封装了取消,获取线程结果,以及状态判断是否取消,是否完成这几个方法,都很有用。

demo:

使用线程池提交Callable接口任务,返回Future接口,添加进list,最后遍历FutureList且内部使用while轮询,并发获取结果

 package thread;

 import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; /**
* @author denny.zhang
* @ClassName: FutureDemo
* @Description: Future多线程并发任务结果归集
* @date 2016年11月4日 下午1:50:32
*/
public class FutureDemo { public static void main(String[] args) {
Long start = System.currentTimeMillis();
//开启多线程
ExecutorService exs = Executors.newFixedThreadPool(10);
try {
//结果集
List<Integer> list = new ArrayList<Integer>();
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
//1.高速提交10个任务,每个任务返回一个Future入list
for (int i = 0; i < 10; i++) {
futureList.add(exs.submit(new CallableTask(i + 1)));
}
Long getResultStart = System.currentTimeMillis();
System.out.println("结果归集开始时间=" + new Date());
//2.结果归集,用迭代器遍历futureList,高速轮询(模拟实现了并发),任务完成就移除
while(futureList.size()>0){
Iterator<Future<Integer>> iterable = futureList.iterator();
//遍历一遍
while(iterable.hasNext()){
Future<Integer> future = iterable.next();
//如果任务完成取结果,否则判断下一个任务是否完成
if (future.isDone() && !future.isCancelled()){
//获取结果
Integer i = future.get();
System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
list.add(i);
//任务完成移除任务
iterable.remove();
}else{
Thread.sleep(1);//避免CPU高速运转,这里休息1毫秒,CPU纳秒级别
}
}
}
System.out.println("list=" + list);
System.out.println("总耗时=" + (System.currentTimeMillis() - start) + ",取结果归集耗时=" + (System.currentTimeMillis() - getResultStart));
} catch (Exception e) {
e.printStackTrace();
} finally {
exs.shutdown();
}
} static class CallableTask implements Callable<Integer> {
Integer i; public CallableTask(Integer i) {
super();
this.i = i;
} @Override
public Integer call() throws Exception {
if (i == 1) {
Thread.sleep(3000);//任务1耗时3秒
} else if (i == 5) {
Thread.sleep(5000);//任务5耗时5秒
} else {
Thread.sleep(1000);//其它任务耗时1秒
}
System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!"+ new Date());
return i;
}
}
}

如上图,开启定长为10的线程池:ExecutorService exs = Executors.newFixedThreadPool(10);+任务1耗时3秒,任务5耗时5秒,其他1秒。控制台打印如下:

结果归集开始时间=Fri Jun 01 09:59:33 CST 2018----起始33秒
task线程:pool-1-thread-3任务i=3,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-2任务i=2,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-4任务i=4,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-6任务i=6,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-7任务i=7,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-8任务i=8,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-9任务i=9,完成!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-10任务i=10,完成!Fri Jun 01 09:59:34 CST 2018
任务i=2获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018---一般任务耗时1秒,33+1=34,验证通过!
任务i=3获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=4获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=6获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=7获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=8获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=9获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
任务i=10获取完成,移出任务队列!Fri Jun 01 09:59:34 CST 2018
task线程:pool-1-thread-1任务i=1,完成!Fri Jun 01 09:59:36 CST 2018
任务i=1获取完成,移出任务队列!Fri Jun 01 09:59:36 CST 2018---任务1 耗时3秒 33+3=36,验证通过!
task线程:pool-1-thread-5任务i=5,完成!Fri Jun 01 09:59:38 CST 2018
任务i=5获取完成,移出任务队列!Fri Jun 01 09:59:38 CST 2018---任务5 耗时5秒 33+5=38,验证通过!
list=[2, 3, 4, 6, 7, 8, 9, 10, 1, 5]--》多执行几遍,最后2个总是1,5最后加进去的,可实现按照任务完成先手顺序获取结果!
总耗时=5012,取结果归集耗时=5002---》符合逻辑,10个任务,定长10线程池,其中一个任务耗时3秒,一个任务耗时5秒,由于并发高速轮训,耗时取最长5秒

建议:此种方法可实现基本目标,任务并行且按照完成顺序获取结果。使用很普遍,老少皆宜,就是CPU有消耗,可以使用

2.FutureTask

原理:

是接口RunnableFuture的唯一实现类。类图如下(网上截取来的。。。我的eclipse类图插件还没装好):

如上图,可见RunnableFuture接口继承自Future<V>+Runnable:

1.Runnable接口,可开启单个线程执行。

2.Future<v>接口,可接受Callable接口的返回值,futureTask.get()阻塞获取结果。

FutureTask的构造方法有两种,其实最终都是赋值callable。如下图:

demo:

demo1:两个步骤:1.开启单个线程执行任务,2.阻塞等待执行结果,分离这两步骤,可在这两步中间穿插别的相关业务逻辑。

 /**
*
* @ClassName:FutureTaskDemo
* @Description:FutureTask弥补了Future必须用线程池提交返回Future的缺陷,实现功能如下:
* 1.Runnable接口,可开启线程执行。
* 2.Future<v>接口,可接受Callable接口的返回值,futureTask.get()阻塞获取结果。
* 这两个步骤:一个开启线程执行任务,一个阻塞等待执行结果,分离这两步骤,可在这两步中间穿插别的相关业务逻辑。
* @author diandian.zhang
* @date 2017年6月16日上午10:36:05
*/
public class FutureTaskContorlDemo { public static void main(String[] args) {
try {
System.out.println("=====例如一个统计公司总部和分部的总利润是否达标100万==========");
//利润
Integer count = 0;
//1.定义一个futureTask,假设去远程http获取各个分公司业绩.
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask());
Thread futureTaskThread = new Thread(futureTask);
futureTaskThread.start();
System.out.println("futureTaskThread start!"+new Date()); //2.主线程先做点别的事
System.out.println("主线程查询总部公司利润开始时间:"+new Date());
Thread.sleep(5000);
count+=10;//北京集团总部利润。
System.out.println("主线程查询总部公司利润结果时间:"+new Date()); //总部已达标100万利润,就不再继续执行获取分公司业绩任务了
if(count>=100){
System.out.println("总部公司利润达标,取消futureTask!"+new Date());
futureTask.cancel(true);//不需要再去获取结果,那么直接取消即可
}else{
System.out.println("总部公司利润未达标,进入阻塞查询分公司利润!"+new Date());
//3总部未达标.阻塞获取,各个分公司结果
Integer i = futureTask.get();//真正执行CallableTask
System.out.println("i="+i+"获取到结果!"+new Date()+new Date());
}
} catch (Exception e) {
e.printStackTrace();
}
} /**
*
* @ClassName:CallableTask
* @Description:一个十分耗时的任务
* @author diandian.zhang
* @date 2017年6月16日上午10:39:04
*/
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("CallableTask-call,查询分公司利润,执行开始!"+new Date());
Thread.sleep(10000);
System.out.println("CallableTask-call,查询分公司利润,执行完毕!"+new Date());
return 10;
}
}

执行结果如下:

=====例如一个统计公司总部和分部的总利润是否达标100万==========
futureTaskThread start!Fri Jun 16 11:14:54 CST 2017----》futureTaskThread 已开始运行
CallableTask-call,查询分公司利润,执行开始!Fri Jun 16 11:14:54 CST 2017
主线程查询总部公司利润开始时间:Fri Jun 16 11:14:54 CST 2017------》主线程耗时5秒
主线程查询总部公司利润结果时间:Fri Jun 16 11:14:59 CST 2017
总部公司利润未达标,进入阻塞查询分公司利润!Fri Jun 16 11:14:59 CST 2017
CallableTask-call,查询分公司利润,执行完毕!Fri Jun 16 11:15:04 CST 2017----》futureTaskThread 执行完毕,耗时10秒
i=10获取到结果!Fri Jun 16 11:15:04 CST 2017Fri Jun 16 11:15:04 CST 2017

如上图,分离之后,futureTaskThread耗时10秒期间,主线程还穿插的执行了耗时5秒的操作,大大减小总耗时。且可根据业务逻辑实时判断是否需要继续执行futureTask。

demo2:当然FutureTask一样可以并发执行任务并获取结果,如下:

 package thread;

 import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; /**
*
* @ClassName:FutureTaskDemo
* @Description:FutureTask实现多线程并发执行任务并取结果归集
* @author diandian.zhang
* @date 2017年6月16日上午10:36:05
*/
public class FutureTaskDemo { public static void main(String[] args) {
Long start = System.currentTimeMillis();
//开启多线程
ExecutorService exs = Executors.newFixedThreadPool(10);
try {
//结果集
List<Integer> list = new ArrayList<Integer>();
List<FutureTask<Integer>> futureList = new ArrayList<FutureTask<Integer>>();
//启动线程池,10个任务固定线程数为5
for(int i=0;i<10;i++){
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask(i+1));
//提交任务,添加返回,Runnable特性
exs.submit(futureTask);
//Future特性
futureList.add(futureTask);
}
Long getResultStart = System.currentTimeMillis();
System.out.println("结果归集开始时间="+new Date());
//结果归集
while(futureList.size()>0){
Iterator<FutureTask<Integer>> iterable = futureList.iterator();
//遍历一遍
while(iterable.hasNext()){
Future<Integer> future = iterable.next();
if (future.isDone()&& !future.isCancelled()) {
//Future特性
Integer i = future.get();
System.out.println("任务i=" + i + "获取完成,移出任务队列!" + new Date());
list.add(i);
//任务完成移除任务
iterable.remove();
}else {
//避免CPU高速轮循,可以休息一下。
Thread.sleep(1);
}
}
} System.out.println("list="+list);
System.out.println("总耗时="+(System.currentTimeMillis()-start)+",取结果归集耗时="+(System.currentTimeMillis()-getResultStart));
} catch (Exception e) {
e.printStackTrace();
} finally {
exs.shutdown();
}
} }
CallableTask:
 package thread;

 import java.util.Date;
import java.util.concurrent.Callable; /**
* @Description 回调方法
* @author denny
* @date 2018/8/17 下午3:16
*/
public class CallableTask implements Callable<Integer> {
Integer i; public CallableTask(Integer i) {
super();
this.i = i;
} @Override
public Integer call() throws Exception {
if (i == 1) {
Thread.sleep(3000);//任务1耗时3秒
} else if (i == 5) {
Thread.sleep(5000);//任务5耗时5秒
} else {
Thread.sleep(1000);//其它任务耗时1秒
}
System.out.println("task线程:" + Thread.currentThread().getName() + "任务i=" + i + ",完成!"+ new Date());
return i;
}
}

结果:

 结果归集开始时间=Fri Aug 17 15:51:54 CST 2018
task线程:pool-1-thread-2任务i=2,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-3任务i=3,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-4任务i=4,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-6任务i=6,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-7任务i=7,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-8任务i=8,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-10任务i=10,完成!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-9任务i=9,完成!Fri Aug 17 15:51:55 CST 2018
任务i=8获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=9获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=10获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=2获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=3获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=4获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=6获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
任务i=7获取完成,移出任务队列!Fri Aug 17 15:51:55 CST 2018
task线程:pool-1-thread-1任务i=1,完成!Fri Aug 17 15:51:57 CST 2018
任务i=1获取完成,移出任务队列!Fri Aug 17 15:51:57 CST 2018
task线程:pool-1-thread-5任务i=5,完成!Fri Aug 17 15:51:59 CST 2018
任务i=5获取完成,移出任务队列!Fri Aug 17 15:51:59 CST 2018
list=[8, 9, 10, 2, 3, 4, 6, 7, 1, 5]
总耗时=5014,取结果归集耗时=5007

建议:demo1在特定场合例如有十分耗时的业务但有依赖于其他业务不一定非要执行的,可以尝试使用。demo2多线程并发执行并结果归集,这里多套一层FutureTask比较鸡肋(直接返回Future简单明了)不建议使用。

3.CompletionService:

原理:内部通过阻塞队列+FutureTask,实现了任务先完成可优先获取到,即结果按照完成先后顺序排序。

demo:

 package thread.future;

 import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; /**
*
* @ClassName: CompletionServiceDemo
* @Description: CompletionService多线程并发任务结果归集
* @author denny.zhang
* @date 2016年11月4日 下午1:50:32
*
*/
public class CompletionServiceDemo{ public static void main(String[] args) {
Long start = System.currentTimeMillis();
//开启3个线程
ExecutorService exs = Executors.newFixedThreadPool(5);
try {
int taskCount = 10;
//结果集
List<Integer> list = new ArrayList<Integer>();
//1.定义CompletionService
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(exs);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
//2.添加任务
for(int i=0;i<taskCount;i++){
futureList.add(completionService.submit(new Task(i+1)));
}
//==================结果归集===================
//方法1:future是提交时返回的,遍历queue则按照任务提交顺序,获取结果
// for (Future<Integer> future : futureList) {
// System.out.println("====================");
// Integer result = future.get();//线程在这里阻塞等待该任务执行完毕,按照
// System.out.println("任务result="+result+"获取到结果!"+new Date());
// list.add(result);
// } // //方法2.使用内部阻塞队列的take()
for(int i=0;i<taskCount;i++){
Integer result = completionService.take().get();//采用completionService.take(),内部维护阻塞队列,任务先完成的先获取到
System.out.println("任务i=="+result+"完成!"+new Date());
list.add(result);
}
System.out.println("list="+list);
System.out.println("总耗时="+(System.currentTimeMillis()-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
exs.shutdown();//关闭线程池
} } static class Task implements Callable<Integer>{
Integer i; public Task(Integer i) {
super();
this.i=i;
} @Override
public Integer call() throws Exception {
if(i==5){
Thread.sleep(5000);
}else{
Thread.sleep(1000);
}
System.out.println("线程:"+Thread.currentThread().getName()+"任务i="+i+",执行完成!");
return i;
} }
}

打印结果如下:

线程:pool-1-thread-3任务i=3,执行完成!
线程:pool-1-thread-1任务i=1,执行完成!
线程:pool-1-thread-4任务i=4,执行完成!
线程:pool-1-thread-2任务i=2,执行完成!
任务i==3完成!Fri Jun 16 11:39:17 CST 2017
任务i==1完成!Fri Jun 16 11:39:17 CST 2017
任务i==4完成!Fri Jun 16 11:39:17 CST 2017
任务i==2完成!Fri Jun 16 11:39:17 CST 2017
线程:pool-1-thread-4任务i=8,执行完成!
线程:pool-1-thread-3任务i=7,执行完成!
线程:pool-1-thread-1任务i=6,执行完成!
线程:pool-1-thread-2任务i=9,执行完成!
任务i==8完成!Fri Jun 16 11:39:18 CST 2017
任务i==7完成!Fri Jun 16 11:39:18 CST 2017
任务i==6完成!Fri Jun 16 11:39:18 CST 2017
任务i==9完成!Fri Jun 16 11:39:18 CST 2017
线程:pool-1-thread-3任务i=10,执行完成!
任务i==10完成!Fri Jun 16 11:39:19 CST 2017
线程:pool-1-thread-5任务i=5,执行完成!
任务i==5完成!Fri Jun 16 11:39:21 CST 2017
list=[3, 1, 4, 2, 8, 7, 6, 9, 10, 5]---》这里证实了确实按照执行完成顺序排序
总耗时=5004---》符合逻辑,10个任务,定长5线程池执行,取最长时间。

建议:使用率也挺高,而且能按照完成先后排序,建议如果有排序需求的优先使用。只是多线程并发执行任务结果归集,也可以使用。

4.CompletableFuture

原理:

4.1.从注释看

JDK1.8才新加入的一个实现类,实现了Future<T>, CompletionStage<T>2个接口,JDK注释如下图:

译文(没兴趣的可以跳过):

当一个Future可能需要显示地完成时,使用CompletionStage接口去支持完成时触发的函数和操作。当2个以上线程同时尝试完成、异常完成、取消一个CompletableFuture时,只有一个能成功。

CompletableFuture实现了CompletionStage接口的如下策略:

1.为了完成当前的CompletableFuture接口或者其他完成方法的回调函数的线程,提供了非异步的完成操作。

2.没有显式入参Executor的所有async方法都使用ForkJoinPool.commonPool()为了简化监视、调试和跟踪,所有生成的异步任务都是标记接口AsynchronousCompletionTask的实例。

3.所有的CompletionStage方法都是独立于其他共有方法实现的,因此一个方法的行为不会受到子类中其他方法的覆盖。

CompletableFuture实现了Futurre接口的如下策略:

1.CompletableFuture无法直接控制完成,所以cancel操作被视为是另一种异常完成形式。方法isCompletedExceptionally可以用来确定一个CompletableFuture是否以任何异常的方式完成。

2.以一个CompletionException为例,方法get()和get(long,TimeUnit)抛出一个ExecutionException,对应CompletionException。为了在大多数上下文中简化用法,这个类还定义了方法join()和getNow,而不是直接在这些情况中直接抛出CompletionException。

4.2.CompletionStage接口实现流式编程:

JDK8新增接口,此接口包含38个方法...是的,你没看错,就是38个方法。这些方法主要是为了支持函数式编程中流式处理。

4.3.CompletableFuture中4个异步执行任务静态方法:

如上图,其中supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池,注意:这些线程都是Daemon线程,主线程结束Daemon线程不结束,只有JVM关闭时,生命周期终止

4.4.组合CompletableFuture:

thenCombine(): 先完成当前CompletionStage和other 2个CompletionStage任务,然后把结果传参给BiFunction进行结果合并操作。

thenCompose():第一个CompletableFuture执行完毕后,传递给下一个CompletionStage作为入参进行操作。

demo:

JDK CompletableFuture  自带多任务组合方法allOf和anyOf

allOf是等待所有任务完成,构造后CompletableFuture完成

anyOf是只要有一个任务完成,构造后CompletableFuture就完成

方式一:循环创建CompletableFuture list,调用sequence()组装返回一个有返回值的CompletableFuture,返回结果get()获取

方式二:全流式处理转换成CompletableFuture[]+allOf组装成一个无返回值CompletableFuture,join等待执行完毕。返回结果whenComplete获取。---》推荐

 package thread;

 import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream; import com.google.common.collect.Lists; /**
*
* @ClassName:CompletableFutureDemo
* @Description:多线程并发任务,取结果归集
* @author diandian.zhang
* @date 2017年6月14日下午12:44:01
*/
public class CompletableFutureDemo {
public static void main(String[] args) {
Long start = System.currentTimeMillis();
//结果集
List<String> list = new ArrayList<String>();
List<String> list2 = new ArrayList<String>();
//定长10线程池
ExecutorService exs = Executors.newFixedThreadPool(10);
List<CompletableFuture<String>> futureList = new ArrayList<>();
final List<Integer> taskList = Lists.newArrayList(2,1,3,4,5,6,7,8,9,10);
try {
////方式一:循环创建CompletableFuture list,调用sequence()组装返回一个有返回值的CompletableFuture,返回结果get()获取
//for(int i=0;i<taskList.size();i++){
// final int j=i;
// //异步执行
// CompletableFuture<String> future = CompletableFuture.supplyAsync(()->calc(taskList.get(j)), exs)
// //Integer转换字符串 thenAccept只接受不返回不影响结果
// .thenApply(e->Integer.toString(e))
// //如需获取任务完成先后顺序,此处代码即可
// .whenComplete((v, e) -> {
// System.out.println("任务"+v+"完成!result="+v+",异常 e="+e+","+new Date());
// list2.add(v);
// })
// ;
// futureList.add(future);
//}
////流式获取结果:此处是根据任务添加顺序获取的结果
//list = sequence(futureList).get(); //方式二:全流式处理转换成CompletableFuture[]+组装成一个无返回值CompletableFuture,join等待执行完毕。返回结果whenComplete获取
CompletableFuture[] cfs = taskList.stream().map(object-> CompletableFuture.supplyAsync(()->calc(object), exs)
.thenApply(h->Integer.toString(h))
//如需获取任务完成先后顺序,此处代码即可
.whenComplete((v, e) -> {
System.out.println("任务"+v+"完成!result="+v+",异常 e="+e+","+new Date());
list2.add(v);
})).toArray(CompletableFuture[]::new);
//等待总任务完成,但是封装后无返回值,必须自己whenComplete()获取
CompletableFuture.allOf(cfs).join();
System.out.println("任务完成先后顺序,结果list2="+list2+";任务提交顺序,结果list="+list+",耗时="+(System.currentTimeMillis()-start));
} catch (Exception e) {
e.printStackTrace();
}finally {
exs.shutdown();
}
} public static Integer calc(Integer i){
try {
if(i==1){
//任务1耗时3秒
Thread.sleep(3000);
}else if(i==5){
//任务5耗时5秒
Thread.sleep(5000);
}else{
//其它任务耗时1秒
Thread.sleep(1000);
}
System.out.println("task线程:"+Thread.currentThread().getName()+"任务i="+i+",完成!+"+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
return i;
} /**
*
* @Description 组合多个CompletableFuture为一个CompletableFuture,所有子任务全部完成,组合后的任务才会完成。带返回值,可直接get.
* @param futures List
* @return
* @author diandian.zhang
* @date 2017年6月19日下午3:01:09
* @since JDK1.8
*/
public static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
//1.构造一个空CompletableFuture,子任务数为入参任务list size
CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
//2.流式(总任务完成后,每个子任务join取结果,后转换为list)
return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
} /**
*
* @Description Stream流式类型futures转换成一个CompletableFuture,所有子任务全部完成,组合后的任务才会完成。带返回值,可直接get.
* @param futures Stream
* @return
* @author diandian.zhang
* @date 2017年6月19日下午6:23:40
* @since JDK1.8
*/
public static <T> CompletableFuture<List<T>> sequence(Stream<CompletableFuture<T>> futures) {
List<CompletableFuture<T>> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
return sequence(futureList);
}
}

方式二返回结果:

task线程:pool-1-thread-1任务i=2,完成!+Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-9任务i=9,完成!+Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-6任务i=6,完成!+Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-8任务i=8,完成!+Mon Jun 19 18:26:17 CST 2017
任务6完成!result=6,异常 e=null,Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-4任务i=4,完成!+Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-7任务i=7,完成!+Mon Jun 19 18:26:17 CST 2017
任务4完成!result=4,异常 e=null,Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-3任务i=3,完成!+Mon Jun 19 18:26:17 CST 2017
任务3完成!result=3,异常 e=null,Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-10任务i=10,完成!+Mon Jun 19 18:26:17 CST 2017
任务10完成!result=10,异常 e=null,Mon Jun 19 18:26:17 CST 2017
任务7完成!result=7,异常 e=null,Mon Jun 19 18:26:17 CST 2017
任务8完成!result=8,异常 e=null,Mon Jun 19 18:26:17 CST 2017
任务2完成!result=2,异常 e=null,Mon Jun 19 18:26:17 CST 2017
任务9完成!result=9,异常 e=null,Mon Jun 19 18:26:17 CST 2017
task线程:pool-1-thread-2任务i=1,完成!+Mon Jun 19 18:26:19 CST 2017---》任务1耗时3秒
任务1完成!result=1,异常 e=null,Mon Jun 19 18:26:19 CST 2017
task线程:pool-1-thread-5任务i=5,完成!+Mon Jun 19 18:26:21 CST 2017---》任务5耗时5秒
任务5完成!result=5,异常 e=null,Mon Jun 19 18:26:21 CST 2017
list2=[6, 4, 3, 10, 7, 8, 2, 9, 1, 5]list=[],耗时=5076---》符合逻辑,10个任务,10个线程并发执行,其中任务1耗时3秒,任务5耗时5秒,耗时取最大值。

建议:CompletableFuture满足并发执行,顺序完成先手顺序获取的目标。而且支持每个任务的异常返回,配合流式编程,用起来速度飞起。JDK源生支持,API丰富,推荐使用。

5.总结:

本文从原理、demo、建议三个方向分析了常用多线程并发,取结果归集的几种实现方案,希望对大家有所启发,整理表格如下:

Futrue

FutureTask

CompletionService

CompletableFuture

原理

Futrue接口

接口RunnableFuture的唯一实现类,RunnableFuture接口继承自Future<V>+Runnable: 内部通过阻塞队列+FutureTask接口 JDK8实现了Future<T>, CompletionStage<T>2个接口
多任务并发执行 支持 支持 支持 支持
获取任务结果的顺序

支持任务完成先后顺序

 未知  支持任务完成的先后顺序 支持任务完成的先后顺序
异常捕捉  自己捕捉  自己捕捉  自己捕捉 源生API支持,返回每个任务的异常
建议 CPU高速轮询,耗资源,可以使用,但不推荐  功能不对口,并发任务这一块多套一层,不推荐使用。  推荐使用,没有JDK8CompletableFuture之前最好的方案,没有质疑

API极端丰富,配合流式编程,速度飞起,推荐使用!

=====参考=================

1.《JAVA高并发程序设计》

2.使用Java 8的CompletableFuture实现函数式的回调

3.Java CompletableFuture 详解

多线程并发执行任务,取结果归集。终极总结:Future、FutureTask、CompletionService、CompletableFuture的更多相关文章

  1. fastDFS多线程并发执行出现的问题

    ---------------------  原作者:Java高级开发  来源:CSDN  原文:https://blog.csdn.net/hang1995/article/details/7924 ...

  2. java多线程并发执行demo,主线程阻塞

    其中有四个知识点我单独罗列了出来,属于多线程编程中需要知道的知识: 知识点1:X,T为泛型,为什么要用泛型,泛型和Object的区别请看:https://www.cnblogs.com/xiaoxio ...

  3. Android并发编程之白话文详解Future,FutureTask和Callable

    从最简单的说起Thread和Runnable 说到并发编程,就一定是多个线程并发执行任务.那么并发编程的基础是什么呢?没错那就是Thread了.一个Thread可以执行一个Runnable类型的对象. ...

  4. HashMap多线程并发问题分析

    转载: HashMap多线程并发问题分析 并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题. ...

  5. Java 多线程 并发编程

    一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...

  6. 多线程并发 synchronized对象锁的控制与优化

    本文针对用户取款时多线程并发情境,进行相关多线程控制与优化的描述. 首先建立用户类UserTest.业务操作类SynchronizedTest.数据存取类DataStore,多线程测试类MultiTh ...

  7. python多进程并发和多线程并发和协程

    为什么需要并发编程? 如果程序中包含I/O操作,程序会有很高的延迟,CPU会处于等待状态,这样会浪费系统资源,浪费时间 1.Python的并发编程分为多进程并发和多线程并发 多进程并发:运行多个独立的 ...

  8. Python_关于多线程下变量赋值取值的一点研究

    关于多线程下变量赋值取值的一点研究 by:授客 QQ:1033553122 1.代码实践1 #!/usr/bin/env python # -*- coding:utf-8 -*- __author_ ...

  9. 尝试php命令行脚本多进程并发执行

    php不支持多线程,但是我们可以把问题转换成“多进程”来解决.由于php中的pcntl_fork只有unix平台才可以使用,所以本文尝试使用popen来替代.  下面是一个例子:  被并行调用的子程序 ...

随机推荐

  1. ssh整合时报出的异常及解决办法

    com.opensymphony.xwork2.inject.DependencyException: com.opensymphony.xwork2.inject.ContainerImpl$Mis ...

  2. sql关键字之null

    在数据库中使用一种特殊的值表示未知的值--NULL,我们称之为空值但并不是空的字符串,而是特殊的值.

  3. [瞎玩儿系列] 使用SQL实现Logistic回归

    本来想发在知乎专栏的,但是文章死活提交不了,我也是醉了,于是乎我就干脆提交到CNBLOGS了. 前言 前段时间我们介绍了Logistic的数学原理和C语言实现,而我呢?其实还是习惯使用Matlab进行 ...

  4. UNIX 系统概述

    UNIX体系结构(UNIX Architecture) 调用内核的接口叫做系统调用(system call,图1.1中的阴影部分),普通函数库是建立在系统调用接口的基础之上.应用(applicatio ...

  5. 生产环境中使用Docker Swarm的一些建议

    译者按: 实践中会发现,生产环境中使用单个Docker节点是远远不够的,搭建Docker集群势在必行.然而,面对Kubernetes, Mesos以及Swarm等众多容器集群系统,我们该如何选择呢?它 ...

  6. 如何将angular-ui的图片轮播组件封装成一个指令

    在项目开发中我们经常会遇到图片轮播的功能点: 如果我们开发人员自己原生手写,将会花费很多的时间,最终得不偿失. 接下来就详细说说如何使用angular-ui发热图片轮播模块,并且将它写成一个指令(便于 ...

  7. CSS之BFC及其应用

    BFC是Block Formatting Context的缩写,直译过来就是"块级格式化上下文".先不管它到底是什么,看一个例子: .parent{ border: 1px sol ...

  8. POJ1182食物链(并查集)

    Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到 ...

  9. javaWeb学习总结(3)- 通过Servlet生成验证码图片(2)

    一.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类,如下:

  10. 用css3过滤做遮罩效果

    <!DOCTYPE html><html ng-app="myApp" ng-controller="myController">< ...