开启线程池并需要获取结果归集的情况下,如何实现,以及优劣,下面是任务执行完,结果归集时,几种方式:
一.ExecutorService+Futrue
原理:
Future接口封装了取消,获取线程结果,以及状态判断是否取消,是否完成这几个方法
demo:
使用线程池提交Callable接口任务,返回Future接口,添加进list,最后遍历FutureList且内部使用while轮询,并发获取结果
(1)、创建线程池
ExecutorService exs = Executors.newFixedThreadPool(10);
(2)、创建Future结果集list
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
(3)、提交10个任务,每个任务结果返回到FutureList里面
for (int i = 0; i < 10; i++) {
futureList.add(exs.submit(new CallableTask(i + 1)));
}
(4)、结果归集,用迭代器遍历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();
//任务完成移除任务
iterable.remove();
}
}
}
总结:
将线程执行结果存入FutureList里面,然后轮询这个list,
通过迭代器获取到对应的Future,用future.get()来获取结果。
建议:
这种方式取结果,如果没有取到会产生阻塞,直到取到结果才会执行下一个future.get(),
所以适用于结果集要保持顺序的情况。
二.ExecutorService+FutureTask
原理:
是接口RunnableFuture的唯一实现类。类图如下
如上图,可见RunnableFuture接口继承自Future+Runnable:
所以又有run方法,又有Future接口的get等方法。
1.Runnable接口,可开启单个线程执行。
2.Future接口,可接受Callable接口的返回值,futureTask.get()阻塞获取结果。
FutureTask的构造方法有两种,其实最终都是赋值callable。如下图
demo:
(1)、创建线程池
ExecutorService exs = Executors.newFixedThreadPool(10);
(2)、创建FutureTask任务结果集list
List<FutureTask<Integer>> futureList = new ArrayList<FutureTask<Integer>>();
(3)、提交任务,每个任务结果返回到FutureList里面
for(int i=0;i<10;i++){
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask(i+1));
//Future特性
futureList.add(futureTask);
}
(4)、结果归集,用迭代器遍历futureList,高速轮询,任务完成就移除
while(futureList.size()>0){
Iterator<FutureTask<Integer>> iterable = futureList.iterator();
while(iterable.hasNext()){
Future<Integer> future = iterable.next();
if (future.isDone()&& !future.isCancelled()) {
Integer i = future.get();
//任务完成移除任务
iterable.remove();
}
}
}
总结:
FutureTask和Future其实没太大的区别,
只不过Future的方式是需要用线程池提交任务(exs.submit(new CallableTask(i + 1)))
FutureTask则可以用构造方法提交任务(new FutureTask<Integer>(new CallableTask(i+1));)
同样是取结果是按顺序阻塞的,所以我任务FutureTask这东西有些鸡肋。
建议:
多线程并发执行并结果归集,这里多套一层FutureTask比较鸡肋(直接返回Future简单明了)不建议使用。
三、CompletionService+Future:
原理:
1、使用内部阻塞队列的take()
CompletionService.take()方法,可以按照任务执行完成的顺序获取到对应的FutureTask,
也就是谁先执行完谁先返回,不会按照谁先开始执行谁先返回的顺序,因此也就不会造成阻塞。
2、使用future的阻塞获取方法future.get(),按照提交顺序返回结果,也就是会造成阻塞。
demo:
(1)、创建线程池
ExecutorService exs = Executors.newFixedThreadPool(5);
(2)、定义CompletionService线程服务
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(exs);
(3)、创建FutureList列表
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
(4)、提交任务,每个任务结果返回到FutureList里面
for(int i=0;i<taskCount;i++){
futureList.add(completionService.submit(new Task(i+1)));
}
(5)、结果归集
方法一:
使用future的阻塞获取方法future.get(),按照提交顺序返回结果,也就是会造成阻塞。
for (Future<Integer> future : futureList) {
Integer result = future.get();
}
方法二:
使用内部阻塞队列的take(),也就是谁先执行完谁先返回,不会造成阻塞。
Integer result = completionService.take().get();
总结:
可以按照提交顺序阻塞返回结果,也可以按照执行完成顺序非阻塞返回结果。
建议:
能按照完成先后排序,建议如果有排序需求的优先使用。
只是多线程并发执行任务结果归集(非阻塞,按照任务完成顺序返回),也可以使用。
四、CompletableFuture:
原理:
JDK1.8才新加入的一个实现类,实现了Future, CompletionStage2个接口。
实现CompletionStage接口是为了实现流式编程。
CompletableFuture中4个异步执行任务静态方法:
supplyAsync用于有返回值的任务
runAsync则用于没有返回值的任务
Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池,
注意:这些线程都是Daemon线程,主线程结束Daemon线程不结束,只有JVM关闭时,生命周期终止。
组合CompletableFuture:
thenCombine():
先完成当前CompletionStage和other 2个CompletionStage任务,
然后把结果传参给BiFunction进行结果合并操作。
thenCompose():
第一个CompletableFuture执行完毕后,
传递给下一个CompletionStage作为入参进行操作。
demo:
CompletableFuture 自带多任务组合方法allOf和anyOf
allOf是等待所有任务完成,构造后CompletableFuture完成。
anyOf是只要有一个任务完成,构造后CompletableFuture就完成。
方式一:循环创建CompletableFuture list,调用sequence()组装返回一个有返回值的CompletableFuture,返回结果get()获取
方式二:全流式处理转换成CompletableFuture[]+allOf组装成一个无返回值CompletableFuture,join等待执行完毕。返回结果whenComplete获取。—》推荐
(1)、创建线程池
ExecutorService exs = Executors.newFixedThreadPool(10);
(2)、创建CompletableFuture集合
List<CompletableFuture<String>> futureList = new ArrayList<>();
(3)、取结果
方式一:
循环创建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)
.thenApply(e->Integer.toString(e))
//如需获取任务完成先后顺序,此处代码即可
.whenComplete((v, e) -> {
System.out.println("任务"+v+"完成!result="+v+",异常 e="+e+","+new Date());
});
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();
总结:
方法一是将任务全部存入到List<CompletableFuture<String>>里面,
然后利用sequence(futureList).get()方法按照添加顺序获取结果,也就是whenComplete里面的v。
方法二是将任务list流式处理,生成一个CompletableFuture[] 数组,
然后通过 CompletableFuture.allOf(cfs).join()方法来获取结果集。
建议:
CompletableFuture满足并发执行,顺序完成可按任务执行完成顺序获取的目标。
而且支持每个任务的异常返回,配合流式编程,用起来速度飞起。JDK源生支持,API丰富,推荐使用。
五、CompletableFuture方法详解
(1)、主动完成计算
getNow有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值。
join返回计算的结果或者抛出一个unchecked异常(CompletionException),
(2)、创建CompletableFuture对象。
Async代表异步执行,run代表执行runable接口的start方法执行,所以无返回值
supply开头的是执行的callable接口的call方法,有返回值。
如果不指定线程池,就默认用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
使用lambda表达式实现异步任务:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
//长时间的计算任务
return "·00";
});
(3)、计算结果完成时的处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:
Async代表异步,不加代表同步
这几个方法的返回值都是CompletableFuture
whenComplete就是写执行逻辑的回调函数
exceptionally方法返回一个新的CompletableFuture,当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算,也就是这个exceptionally方法用来处理异常的情况。
(4)、转换
当原来的CompletableFuture计算完后,将结果传递给函数fn,
将fn的结果作为新的CompletableFuture计算结果
因此它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>。
也就是把上一个CompletableFuture的计算结果传入fn进行转换。
这三个函数的区别和上面介绍的一样,不以Async结尾的方法由原来的线程计算,
以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。
Java的CompletableFuture类总是遵循这样的原则。
与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,
避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。
(5)、纯消费(执行Action)
无返回值类型的方法。
(6)、组合
这一组方法接受一个Function作为参数,这个Function的输入参数是当前的CompletableFuture的计算值,
返回结果将是一个新的CompletableFuture。
也就是把钱一个CompletableFuture的结果作为参数传入带下一个CompletableFuture中执行。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<String> f = future.thenCompose( i -> {
return CompletableFuture.supplyAsync(() -> {
return (i * 10) + "";
});
});
另外一种组合方法,thenCombine,两个CompletionStage是并行执行的,
它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
return "abc";
});
CompletableFuture<String> f = future.thenCombine(future2, (x,y) -> y + "-" + x);
System.out.println(f.get()); //abc-100
(7)、辅助方法
allOf 和 anyOf
用来组合多个CompletableFuture
allOf方法是当所有的CompletableFuture都执行完后执行计算。
anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。