CompletionService的使用和原理解析

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,要使用CompletionService类,你需要在你的项目中添加Java并发包(java.util.concurrent)的依赖。然后,你可以在你的Idea插件的代码中使用CompletionService类来实现多线程任务的并行处理和结果的收集。 下面是一个简单的示例代码,演示如何使用CompletionService类: ```java import java.util.concurrent.*; public class MyPlugin { public void performTasks() { ExecutorService executor = Executors.newFixedThreadPool(5); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); for (int i = 0; i < 10; i++) { final int taskNumber = i; completionService.submit(() -> { // 这里是你想要执行的任务代码 // 例如:调用一个API或者从本地读取一个文件 return "Task " + taskNumber + " completed successfully"; }); } for (int i = 0; i < 10; i++) { try { Future<String> result = completionService.take(); System.out.println(result.get()); } catch (InterruptedException | ExecutionException e) { // 处理异常 } } executor.shutdown(); } } ``` 在这个示例代码中,我们创建了一个固定大小为5的线程池,然后用CompletionService包装这个线程池。接着,我们循环提交10个任务到CompletionService中。每个任务都是一个Lambda表达式,返回一个字符串表示任务执行的结果。 在任务提交完成之后,我们循环调用CompletionService的take()方法,该方法会返回一个Future对象,表示一个任务的执行结果。由于我们提交了10个任务,所以我们需要调用10次take()方法。当一个任务完成时,take()方法就会返回,我们可以从Future对象中获取任务的执行结果,并打印出来。 最后,我们关闭线程池,释放资源。 这就是如何使用CompletionService类来实现多线程任务的并行处理和结果的收集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值