CompletionService的使用和原理解析

本文介绍如何使用CompletionService进行数据库元数据批量获取的并行处理,通过ExecutorCompletionService实现多线程任务的管理和结果聚合,提高处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在工作中遇到一个定时去抽取数据库表的元数据信息,然后做一些其他的业务的处理。显然通过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()函数
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值