多线程 | CompletionService异步非阻塞获取并行任务执行结果

多线程 | CompletionService异步获取并行任务执行结果,主要分两点来讨论CompletionService:

1)使用方式
2)源码分析

1)使用方式

使用Future和Callable可以获取线程执行结果,但获取方式确实阻塞的,根据添加到线程池中的线程顺序,依次获取,获取不到就阻塞。
为了解决这种情况,可以采用轮询的做法。

这里主要讨论用 CompletionService 来实现异步快速收集线程执行结果。
//初始化线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);

    CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
    for (int j = 1; j <= 5; j++) {

        final int index = j;
        completionService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //第三个线程睡眠等待
                if (index == 3) {
                    java.lang.Thread.sleep(3000l);
                }
                return index;
            }
        });
    }
    threadPool.shutdown();

    for (int i = 0; i < 5; i++) {
        try {
            System.out.println("线程:"+completionService.take().get()+" 任务执行结束:");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

获取的结果是无序的,第三个睡眠线程始终最后结束,也证实了获取结果的方式是非阻塞的。如下:

线程:2 任务执行结束
线程:1 任务执行结束
线程:5 任务执行结束
线程:4 任务执行结束
线程:3 任务执行结束

2)源码分析

首先看一下 构造方法:

  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>>();
}

构造法方法主要初始化了一个阻塞队列,用来存储已完成的task任务。
然后看一下 completionService.submit 方法:

public Future<V> submit(Callable<V> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<V> f = newTaskFor(task);
    //将我们的callable任务包装成QueueingFuture
    executor.execute(new QueueingFuture(f));
    return f;
}

可以看到,callable任务被包装成QueueingFuture,而 QueueingFuture是 FutureTask的子类,所以最终执行了FutureTask中的run()方法。来看一下该方法:

 public void run() {
 //判断执行状态,保证callable任务只被运行一次
    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 {
            //这里回调我们创建的callable对象中的call方法
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
            //处理执行结果
                set(result);
        }
    } finally {
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

可以看到在该 FutureTask 中执行run方法,最终回调自定义的callable中的call方法,执行结束之后,通过 set(result) 处理执行结果:

    protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    //设置执行结果v,并标记线程执行状态为:NORMAL
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); 
        //完成执行,将执行结果添加到队列
        finishCompletion();
    }
}

继续跟进finishCompletion()方法,在该方法中找到 done()方法:

protected void done() { completionQueue.add(task); }

    可以看到该方法只做了一件事情,就是将执行结束的task添加到了队列中,只要队列中有元素,我们调用take()方法时就可以获得执行的结果。
    到这里就已经清晰了,异步非阻塞获取执行结果的实现原理其实就是通过队列来实现的,FutureTask将执行结果放到队列中,先进先出,线程执行结束的顺序就是获取结果的顺序。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页