1 例子
10个任务异步并发执行,如果有任务完成,则实时获取任务结果。
/**
* 多任务异步并发执行,并实时获取已完成任务的结果
*
* @author chaozai
* @date 2019年6月21日
*
*/
public class CompletionServiceTest {
public static void main(String[] args) {
//线程池
ExecutorService exec = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
//阻塞队列
BlockingQueue<Future<Integer>> queue = new LinkedBlockingDeque<Future<Integer>>(10);
//执行任务服务
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(exec, queue);
long start = System.currentTimeMillis();
System.out.println("start:" + DateFormatUtils.format(start, "yyyy-MM-dd hh:mm:ss:SSS"));
// 提交10次任务
for (int i = 0; i < 10; i++) {
completionService.submit(new Task());
}
int totalTime = 0;
//提取10次任务结果
for (int i = 0; i < 10; i++) {
try {
Future<Integer> f = completionService.take();
System.out.println(f.get());
totalTime = totalTime+f.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//任务各自执行时间的总和
System.out.println("total time:"+totalTime);
long end = System.currentTimeMillis();
System.out.println("end:" + DateFormatUtils.format(end, "yyyy-MM-dd hh:mm:ss:SSS"));
//所有任务实际消耗时间
System.out.println("tasks coust ms:" + (end - start));
}
}
/**
* 任务:休眠随机秒数,并返回休眠时间
*
* @author chaozai
* @date 2019年6月21日
*
*/
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int random = new Random().nextInt(1000);
Thread.sleep(random);
System.out.println(Thread.currentThread().getName() + " sleep " + random + " ms");
return random;
}
}
结果:发现如果任务同步按序执行,则需要4秒多,异步并发执行则只使用1秒,而且已完成的任务会实时返回结果
start:2019-06-21 05:13:20:335
pool-1-thread-6 sleep 47 ms
47
pool-1-thread-2 sleep 118 ms
118
pool-1-thread-4 sleep 147 ms
147
pool-1-thread-5 sleep 226 ms
226
pool-1-thread-10 sleep 353 ms
353
pool-1-thread-9 sleep 373 ms
373
pool-1-thread-7 sleep 560 ms
560
pool-1-thread-8 sleep 856 ms
856
pool-1-thread-3 sleep 931 ms
931
pool-1-thread-1 sleep 968 ms
968
total time:4579
end:2019-06-21 05:13:21:481
sync coust ms:1146
2 原理
- 异步并发执行
这个很简单,就是多线程执行Callable类型的任务,查看ExecutorCompletionService源码:
任务提交:线程池executor执行了QueueingFuture任务,咦?这个没听过,原来是个ExecutorCompletionService内部定义的一个FutureTask子类。为何要单独定义一个Future呢?核心就在要和阻塞队列进行绑定,它重写了FutureTask的done方法,当任务执行结束,那么将任务结果放到阻塞队列中。
completionService.submit(new Task());
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
- 已完成的任务会实时返回结果
ExecutorCompletionService中take方法,实际上是调用了阻塞队列的take方法:
completionService.take();
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
再看看阻塞队列LinkedBlockingDeque中take的实现:如果队列为空,即任务还未执行完,则进行等待,任务结束后会唤醒等待条件notEmpty,此时将结果返回。
public E take() throws InterruptedException {
return takeFirst();
}
public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
//Removes and returns first element, or null if empty.
while ( (x = unlinkFirst()) == null)
//if empty,then wait
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
3 其他
ExecutorService.invokeAll也可以批量执行异步任务集合,但是它需要等待所有任务都执行完成,才会将结果一次性返回。所以看大家的需求是及时获取,还是等待全部完成(缺一不可)。
4 思考
那么自己能不能实现一个类似CompletionService的机制呢?用基础的Runnable实现?
上代码:
/**
* 使用Runnable实现CompletionService机制,实时获取结果功能
*/
public class SelfCompletionServiceTest {
public static void main(String[] args) {
//线程池
ExecutorService exec = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
long start = System.currentTimeMillis();
System.out.println("start:" + DateFormatUtils.format(start, "yyyy-MM-dd hh:mm:ss:SSS"));
// 提交10次任务
for (int i = 0; i < 10; i++) {
exec.execute(new Task());
}
int totalTime = 0;
//提取10次任务结果
for (int i = 0; i < 10; i++) {
try {
int result = Task.get();
System.out.println(result);
totalTime = totalTime + result;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//任务各自执行时间的总和
System.out.println("total time:" + totalTime);
long end = System.currentTimeMillis();
System.out.println("end:" + DateFormatUtils.format(end, "yyyy-MM-dd hh:mm:ss:SSS"));
//所有任务实际消耗时间
System.out.println("tasks coust ms:" + (end - start));
}
}
/**
* 任务:休眠随机秒数,并返回休眠时间
*
* @author chaozai
* @date 2019年6月21日
*/
class Task implements Runnable {
private static BlockingQueue<Integer> OVER_JOB_QUEUE = new LinkedBlockingDeque<Integer>(10);
private static byte[] LOCK = new byte[0];//创建最简易的lock
public void run() {
int random = new Random().nextInt(1000);
try {
Thread.sleep(random);
add(random);
System.out.println(Thread.currentThread().getName() + " sleep " + random + " ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void add(int number) {
OVER_JOB_QUEUE.add(number);
}
public static int get() throws InterruptedException {
return OVER_JOB_QUEUE.take();
}
}
结果:和CompletionService一样,比串行执行,效率快了5倍以上!
start:2019-11-13 10:02:48:425
pool-1-thread-7 sleep 80 ms
80
pool-1-thread-4 sleep 345 ms
345
pool-1-thread-6 sleep 436 ms
436
pool-1-thread-8 sleep 498 ms
498
pool-1-thread-10 sleep 509 ms
509
pool-1-thread-2 sleep 577 ms
577
pool-1-thread-9 sleep 608 ms
608
pool-1-thread-1 sleep 722 ms
722
pool-1-thread-3 sleep 772 ms
772
pool-1-thread-5 sleep 813 ms
813
total time:5360
end:2019-11-13 10:02:49:367
tasks coust ms:942
思考:不要迷恋Future,CompletionService这些新花样,获取线程运行结果?实时提取最新结果集?这些靠最基本的Runnable和BlockingQueue,都能实现!实际上,他们也是基于这些实现的。
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!