既然是简单的区别,那么就直接上代码吧
包装返回值
//简单返回结果
static class Result<T>{
T o;
public Result(T t) {
o=t;
}
}
ExecutorService 操作模式
/**
* ExecutorService 操作模式
*
* 可主动设置return返回值,等待所有的结果集返回...
*/
public static void doExecutor(ExecutorService executor,List<Callable<Result<String>>> list) {
ArrayList<FutureTask<Result<String>>> futureTasks=new ArrayList<>();
for(Callable<Result<String>> callable: list){
FutureTask<Result<String>> futureTask= (FutureTask<Result<String>>)executor.submit(
callable
);
futureTasks.add(futureTask);
}
for (FutureTask<Result<String>> task : futureTasks) {
try {
Result<String> result=task.get();//阻塞式
task.get(1000,TimeUnit.SECONDS);//阻塞指定时间式 ->抛出TimeoutException
//do result
} catch (InterruptedException | ExecutionException | TimeoutException e) {
//捕获异常 InterruptedException todo other things
//捕获异常 TimeoutException todo other things
//捕获异常 ExecutionException 执行异常,线程可能被中断 抛出?
e.printStackTrace();
}
}
}
ExecutorCompletionService<> 操作模式
/**
* ExecutorCompletionService<> 操作模式
*
* 可主动设置return返回值,等待所有的结果集返回...
*/
public static void doExecutorCompletionService(Executor executor,List<Callable<Result<String>>> list) {
ExecutorCompletionService<Result<String>> service=new ExecutorCompletionService<>(executor);//根据个人实现可以选择其他形式
for(Callable<Result<String>> callable: list){
//可不用主动收集返回值,在内部维护了阻塞队列,用以存储FutureTask的结果放入至队列中
service.submit(
callable
);
}
for (int i = 0; i < list.size(); i++) {
try {
//阻塞式 正常来说,线程执行结束,task状态已经变更为NORNAL才会执行done,
// 这个时候才会将结果集放入至阻塞队列中,在此之前take一直阻塞 ,同时get方法就直接返回
Future<Result<String>> task = service.take();
service.poll();//非阻塞 立即返回结果
service.poll(1000,TimeUnit.SECONDS);//等待指定时间式
Result<String> result=task.get();//此时就不用阻塞直接返回结果集
//do result
} catch (InterruptedException | ExecutionException e) {
//捕获异常 InterruptedException todo other things
//捕获异常 ExecutionException 执行异常,线程可能被中断 抛出?
e.printStackTrace();
}
}
}
main
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Callable<Result<String>>> callableList=new ArrayList<>();
callableList.add(()-> new Result<>("1"));
//ExecutorService 操作模式
doExecutor(executor,callableList);
List<Callable<Result<String>>> callableList1=new ArrayList<>();
callableList1.add(() -> new Result<>("2"));
//ExecutorCompletionService<> 操作模式
doExecutorCompletionService(executor,callableList1);
}
区别
- ExecutorCompletionService<> 操作模式 仅仅在内部维护了阻塞队列,用以存储FutureTask的结果放入至队列中
- 区别在于,submit方法是否需要一个集合来保存结果集,ExecutorService需要主动创建一个集合,
- 而ExecutorCompletionService 内部集成了阻塞队列用以保存结果集
后续学到更多,再来补充
2020年3月20日14点45分补充
在疫情期间看完了《Java并发编程实战》,其中提到了ExecutorCompletionService这个对象。
举个例子
比如,页面上有5个不同的网站的广告位,然后我们需要将其渲染至前端页面上。
方案一 图片和文字同步加载
- loading html text …
- loading five images…
- show page
效果怎么样?
1、同步加载,导致页面刷新效率过低,用户体验差;
2、如果从其他网站加载广告位的时候太慢,后台在同步阻塞,这个就很蛋疼了把。
方案二 图片异步加载
- loading html text …
- async loading five images…
- show page
效果怎么样?
1、页面正常文字可观,且立马加载,用户体验非常好。
2、图片采用文本框预留,加载好了直接使用
3、这个时候虽然是启用了5个线程加载的五个广告(假设分别为A,B,C,D,E),也就是产生了List<Future> ans
对象,因为我们不清楚哪个网站的延迟最低,能让我们优先取得结果是不是?
假设list中的数组就是按照[A,B,C,D,E]存储,那么我们在get的时候,阻塞取结果集的时候,第一个拿的就是A,这个时候A恰好是延迟最高的那个,这个时候B,C,D,E都已经返回了广告在Future中set了,这个时候只有等A完成后,才能迭代到B把,那你说,虽然能异步加载,但是感觉差了点意思,就不能那个广告好了,我页面就加载那个显示处理?我看到别人的页面都是这样的啊,是吧!
方案三 图片异步加载,采用ExecutorCompletionService
- loading html text …
- async loading five images…
- show page
效果怎么样?
1、页面正常文字可观,且立马加载,用户体验非常好。
2、图片采用文本框预留,加载好了直接使用
3、先说结果,采用ExecutorCompletionService
的特点是,异步集合任务,谁先完成,谁先进入阻塞队列中,然后页面直接take()
即可,因为take()
只会在队列没有元素的时候,也就是没有图片的时候,才会阻塞,即使A延迟最高,但是我B,C,D,E只要其中一个完成了,那么就会立即add()
至阻塞队列中,这样我页面获取的时候,就可以立马先加载一个图片了是不是,是不是?厉害把!
这还不是最厉害的。假设我们页面有广告加载限制,如果指定3秒时间内,没有加载到图片,那么当前这个任务直接放弃,这个时候正好可以利用阻塞队列的poll(long timeout, TimeUnit unit)
如果指定时间内没有获取到,那么我任性,本次不加载了,当然等待指定时间的wait操作FutureTask也是有的。
探寻ExecutorCompletionService
源码。
public class ExecutorCompletionService<V>
implements CompletionService<V> {
private final Executor executor;
private final AbstractExecutorService aes;
//阻塞队列,本质为链式阻塞队列
private final BlockingQueue<Future<V>> completionQueue;
// 关键点在这里,QueueingFuture 继承了FutureTask并且
//重写了其钩子函数done(){} 然后将结果添加至阻塞队列中
private static class QueueingFuture<V> extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task,
BlockingQueue<Future<V>> completionQueue) {
super(task, null);
this.task = task;
this.completionQueue = completionQueue;
}
private final Future<V> task;
private final BlockingQueue<Future<V>> completionQueue;
//就是它
protected void done() { completionQueue.add(task); }
}
//....其他源码
}
该钩子函数的意义是当任务完成时,添加至阻塞队列。
done被调用不仅限于set任务完成,也包括cancel,和异常捕捉。
总结
ExecutorCompletionService
其重新了FutureTask的钩子方法done()
,并且利用阻塞队列的特性完成任务与结果集的进一步解耦,达到更好的并行获取结果集效果。