最近在工作中遇到一个定时去抽取数据库表的元数据信息,然后做一些其他的业务的处理。显然通过jdbc连接数据库获取表的元数据信息是一个比较耗时的过程,我们后面的逻辑又需要用到这些数据,第一反应是用Future或者FutureTask来处理耗时的流程,如果只是处理单表的元素据获取Future或者FutureTask的时候比较方便,但是我们现在需要处理一批表的元数据获取,我们希望能够批量提交任务,然后在所有的任务都处理完成后获取所有的结果,发现CompletionService能够满足我们的业务场景。
CompletionService使用Demo
public static void testCompleteService() {
ExecutorService threadPool = Executors.newFixedThreadPool(32);
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
for (int i = 0; i < 100; i++) {
Integer index = i;
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
long random = (long) (Math.random() * 10 + 1);
Thread.sleep(3000+random);
return index;
}
});
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
try {
System.out.println("线程:" + completionService.take().get() + " 任务执行结束:");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("总共花费" + (System.currentTimeMillis() - start));
threadPool.shutdown();
}
运行的结果:
线程:2 任务执行结束:
线程:1 任务执行结束:
线程:0 任务执行结束:
线程:5 任务执行结束:
线程:4 任务执行结束:
线程:3 任务执行结束:
线程:8 任务执行结束:
线程:22 任务执行结束:
线程:6 任务执行结束:
线程:16 任务执行结束:
线程:14 任务执行结束:
线程:18 任务执行结束:
………………
从打印的结果来看,我们获取到结果并不是任务提交的顺序,而是任务完成先后的顺序。
原理解析
CompletionService接口的默认实现类是ExecutorCompletionService。
ExecutorCompletionService类的构造方法
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
构造函数传入一个Executor线程池对象,用来处理线程任务,同事初始化一个LinkedBlockingQueue的阻塞队列,用来存储任务执行的结果。
submit方法
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;
}
首先RunnableFuture f = newTaskFor(task)将我们的任务封装成一个RunnableFuture的对象,紧接着将我们的f对象封装成一个QueueingFuture对象提交打线程池。
QueueingFuture类
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;
}
QueueingFuture的继承了类FutureTask类,同时重写了done函数的逻辑,done函数在其父类FutureTask中是一个空函数,在重写逻辑中将我们任务执行的结果放到了LinkedBlockingQueue中,即为将我们的结果聚合到Queue中。
看到这里我们基本上对ExecutorCompletionService有了一个初步的认识,最后的疑惑就是done函数什么时候被调用了?
FutureTask类中run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//回调我们定义的call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
//处理执行的结果
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
在set方法中最终调用finishCompletion函数,在改方法中调用了done()函数