Future如何获取异步结果?

531 阅读2分钟

1.前言

在日常开发中不可避免会出现异步任务,而我们又需要拿到异步任务的执行结果才能继续下一步逻辑。想要拿到异步任务结果,你一定会想到要使用Future类,那么你是否知道Future是如何及时知道异步任务执行完成的呢?在了解原理之前,先来看一下大体流程,然后再一步一步对其进行剖析。

2.异步任务

2.1 创建任务

public static class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        return "异步任务执行完成";
    }
}

2.2 提交线程池执行

Task task = new Task();
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(task);

2.3 构建FutreTask

任务提交给线程池执行后,线程池会把我们的任务构建成FutreTask并返回

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
​
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

2.4 获取异步结果

线程池将构建的FutreTask返回给我们,由于FutreTask实现了Future接口,因此可以通过Future来接收。想要获取结果,只需要调用get()方法即可

String result = future.get();

2.5 get()实现

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

get()逻辑首先判断任务状态是否执行完成,如果没有执行完成就进行阻塞;如果执行完成直接返回任务结果

2.6 阻塞实现

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
​
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // 1. 创建一个节点,结点中包含当前线程对象
        else if (q == null)
            q = new WaitNode();
        // 2. 节点如果不在链表中,就放入链表
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            // 3.在给定时间内阻塞当前线程
            LockSupport.parkNanos(this, nanos);
        }
        else
            // 4.阻塞当前线程
            LockSupport.park(this);
    }
}

阻塞实现逻辑:异步任务未执行完成,根据当前线程创建节点放入链表,然后阻塞当前线程

2.7 异步任务执行完成

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            // 1.遍历链表中的节点,取出结点中的线程
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    // 2.唤醒线程,使得线程可以继续往下执行,获取异步任务执行结果
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }
​
    done();
​
    callable = null;        // to reduce footprint
}

在获取异步任务结果环节,由于异步任务未执行完成,会生成等待节点链表。当异步任务执行完成后,需要遍历等待节点链表,从链表节点中获取阻塞线程对象,对其进行唤醒,使得线程可以继续往下执行,拿到异步任务执行结果