多线程执行任务,取结果归集

开启线程池并需要获取结果归集的情况下,如何实现,以及优劣,下面是任务执行完,结果归集时,几种方式:

一.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执行完后就会执行计算,计算的结果相同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值