FutureTask异步执行任务原理解析

引言:

我们先回顾一下future的使用:

// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交一个Callable任务
Future<Integer> future = executor.submit(new MyTask());
// 在后台执行其他操作
System.out.println("do other operations in main Thread");
// 获取任务结果
try {
    int result = future.get();
    System.out.println("Task result: " + result);

} catch (ExecutionException | InterruptedException e) {
    throw new RuntimeException(e);
}

// 关闭线程池
executor.shutdown();

问题:future.get()会阻塞调用线程,那么它是如何阻塞的呢?在future执行完毕它又是如何被唤醒的呢?

猜想:线程的阻塞是在future.get()中完成的,而线程的唤醒是在异步线程执行完成后进行的。

任务1:future.get()是如何阻塞线程的?

任务2:在异步线程执行完成之后是如何唤醒线程的?

任务1:跟踪future.get()找到它是如何阻塞线程的

先来看下future.get()接口的定义:

// Future.java
/**
 * 等待计算完成,然后检索其结果。
 * Waits if necessary for the computation to complete, and then
 * retrieves its result.
 *
 * @return the computed result
 * @throws CancellationException if the computation was cancelled
 * @throws ExecutionException if the computation threw an
 * exception
 * @throws InterruptedException if the current thread was interrupted
 * while waiting
 */
V get() throws InterruptedException, ExecutionException;

get的作用主要是等待计算完成,然后返回计算结果。可能抛出三种异常。

接下来找到future的实现类,来看一下get方法的具体实现。

跟踪 Future<Integer> future = executor.submit(new MyTask()); 方法,查看返回的future的具体类型。

executor.submit() 同样是一个接口,我们需要找到方法的具体实现。

跟踪 ExecutorService executor = Executors.newFixedThreadPool(1); 方法,查看executor的具体类型。

// Executors.java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());
}

executor的具体类型是ThreadPoolExecutor,接下来查看它对submit方法的实现来找到future的类型。

在跟踪过程中发现ThreadPoolExecutor并不直接实现submit()方法,这个方法在它继承的抽象父类AbstractExecutorService.java中实现:

// AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

这里可以看到submit方法返回的Future的类型是一个RunnableFuture类型的接口,还不是具体类型,需要继续查看RunnableFuture的具体类型是什么,才能找到get方法的具体实现。

跟踪 RunnableFuture<T> ftask = newTaskFor(task); 方法查看ftask的具体类型

// AbstractExecutorService.java
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

找到ftask的具体类型是一个FutureTask,也是future的具体类型,接下来查看FutureTask中get()方法的具体实现,来找到它是如何阻塞当前线程的。

// FutureTask.java
/**
 * The run state of this task, initially NEW.  The run state
 * transitions to a terminal state only in methods set,
 * setException, and cancel.  During completion, state may take on
 * transient values of COMPLETING (while outcome is being set) or
 * INTERRUPTING (only while interrupting the runner to satisfy a
 * cancel(true)). Transitions from these intermediate to final
 * states use cheaper ordered/lazy writes because values are unique
 * and cannot be further modified.
 *
 * Possible state transitions:
 * NEW -> COMPLETING -> NORMAL
 * NEW -> COMPLETING -> EXCEPTIONAL
 * NEW -> CANCELLED
 * NEW -> INTERRUPTING -> INTERRUPTED
 */
private volatile int state;
private static final int NEW          = 0; // 未执行
private static final int COMPLETING   = 1; // 正在执行,尚未完成
private static final int NORMAL       = 2; // 成功完成
private static final int EXCEPTIONAL  = 3; // 执行过程中出现异常
private static final int CANCELLED    = 4; // 任务被取消
private static final int INTERRUPTING = 5; // 任务正在被中断
private static final int INTERRUPTED  = 6; // 任务已经被中断了

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

在这里我们发现FutureTask的实例有很多状态,在get()方法中,当状态为未执行或正在执行时,会调用s = awaitDone(false, 0L); 方法,根据方法名可以猜想阻塞是在这个方法中完成的,所以我们跟踪下这个方法。

// FutureTask.java
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
/**
 * Simple linked list nodes to record waiting threads in a Treiber
 * stack.  See other classes such as Phaser and SynchronousQueue
 * for more detailed explanation.
 */
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}
/**
 * Awaits completion or aborts on interrupt or timeout.
 *
 * @param timed true if use timed waits 是否需要设定等待时间
 * @param nanos time to wait, if timed 等待时间的数值
 * @return state upon completion
 */
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();
        else if (q == null)
            q = new WaitNode();
        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;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

在这个方法中,正常执行过程中,在无限循环中会依次做以下几件事:

  1. 判断等待线程是否被打断,如果被打断则抛出异常并移除等待节点
  2. 判断future的状态是否已经完成,如果已经完成,则将等待节点的线程置空。(等待节点是一个栈型结构,属性有线程和指向另一个等待节点的指针。如果future尚未执行完成,则调用Thread.yield(); 让出cpu时间片,将线程转换为就绪状态。
  3. 如果等待节点为空则新建一个等待节点;
  4. 使用CAS的方式将等待节点压入等待节点栈中(如果还没有压入的情况下)
  5. 如果设置了等待时间,在超时后会移除等待节点并返回状态,没有超时的情况下调用LockSupport.parkNanos(this, nanos);将当前线程阻塞到等待时间结束。
  6. 如果没有设置等待时间,则会调用LockSupport.park(this); 阻塞当前线程,直到线程被打断或调用LockSupport.unPark() 进行唤醒。

在这个方法中,我们可以观察到它创建了一个包含当前线程的等待节点,并通过UNSAFE的比较和交换方法将它压入了future的等待节点栈中,随后调用了LockSupport.park(this)方法阻塞了当前线程。所以说future.get() 是通过LockSupport.park(this) 方法阻塞当前线程的。并且在线程被唤醒后可以返回结果或者错误。那么线程是如何被唤醒的呢?我们开始探究在异步线程执行完成后是如何唤醒线程的。

任务2:探究在异步线程执行完成之后是如何唤醒线程的

因为我们将任务交给线程池执行,而线程池又将任务包装为一个FutureTask,FutureTask实现了RunnableFuture,RunnableFuture继承自Runnable,并且线程的执行一定会调用Runnable接口中的run方法,所以我们从FutureTask的run方法开始跟踪。

// FutureTask.java
/** The underlying callable; nulled out after running */
private Callable<V> callable;

/**
 * Creates a {@code FutureTask} that will, upon running, execute the
 * given {@code Callable}.
 *
 * @param  callable the callable task
 * @throws NullPointerException if the callable is null
 */
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

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

在这个方法中,Callable<V> c = callable; 就是我们在创建FutureTask时传入的参数callable

// AbstractExecutorService.java
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

继续看run方法

// FutureTask.java run方法中的内容
if (c != null && state == NEW) {
    V result;
    boolean ran;
    try {
        result = c.call();
        ran = true;
    } catch (Throwable ex) {
        result = null;
        ran = false;
        setException(ex);
    }
    if (ran)
        set(result);
}

在对callable和state进行检查后,run方法开始执行call()方法中的内容,然后将是否执行完成的表示ran置为true;如果执行成功则会执行set(result),出现异常则会执行setException(ex),到这里已经找到了异步执行完成的后处理方法,唤醒调用线程的操作应该就在这里,我们跟踪进这两个方法中。

// FutureTask.java
/**
 * Sets the result of this future to the given value unless
 * this future has already been set or has been cancelled.
 *
 * <p>This method is invoked internally by the {@link #run} method
 * upon successful completion of the computation.
 *
 * @param v the value
 */
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    outcome = v;
    UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
    finishCompletion();
}
}

/**
 * Causes this future to report an {@link ExecutionException}
 * with the given throwable as its cause, unless this future has
 * already been set or has been cancelled.
 *
 * <p>This method is invoked internally by the {@link #run} method
 * upon failure of the computation.
 *
 * @param t the cause of failure
 */
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

在这两个方法中,首先将state设置为待完成,然后将结果或异常赋值给outcom对象,之后设置状态为完成或异常,之后调用finishCompletion()方法。唤醒调用线程的操作无论是异常还是正常都需要执行,所以继续跟踪。

// FutureTask.java
/**
 * Removes and signals all waiting threads, invokes done(), and
 * nulls out callable.
 */
private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    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
}

在这个方法中,对waiters进行了遍历,waiters也保存了等待节点的栈,在这里依次对所有的等待节点进行唤醒,对所有等待节点中的线程执行 LockSupport.unpark(t);,方法进行唤醒。至此第二个问题也弄清楚了,异步线程执行完成之后会调用LockSupport.unpark(t)唤醒所有等待它执行完成的线程。

线程唤醒后,awaitDone方法将返回FutureTask的状态给get方法,接下来get方法会调用report(s) 方法包装outcom并返回,或抛出异常。

// FutureTask.java
private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

结论:

future.get() 通过调用LockSupport.park() 方法将线程阻塞并创建等待节点WaitNode保存在future对象中,在将future交给线程池执行后future实现的run方法被执行,run方法在callable的call方法执行完毕后会将结果存入outcom中并遍历waiters中的所有等待节点获取阻塞的线程并调用LockSupport.unpark()方法进行唤醒。get() 方法被唤醒后会根据future的状态包装outcom并返回。

延伸:

那么什么是LockSupport.park()和unPark()呢?

// LockSupport.java
/**
 * Disables the current thread for thread scheduling purposes unless the
 * permit is available.
 *
 * <p>If the permit is available then it is consumed and the call returns
 * immediately; otherwise
 * the current thread becomes disabled for thread scheduling
 * purposes and lies dormant until one of three things happens:
 *
 * <ul>
 * <li>Some other thread invokes {@link #unpark unpark} with the
 * current thread as the target; or
 *
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread; or
 *
 * <li>The call spuriously (that is, for no reason) returns.
 * </ul>
 *
 * <p>This method does <em>not</em> report which of these caused the
 * method to return. Callers should re-check the conditions which caused
 * the thread to park in the first place. Callers may also determine,
 * for example, the interrupt status of the thread upon return.
 *
 * @param blocker the synchronization object responsible for this
 *        thread parking
 * @since 1.6
 */
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    // 设置线程的阻塞对象,便于调试和监控线程的状态。
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

调用Unsafe.getUnsafe().park(false, 0L);

// Unsafe.java
/**
 * Block current thread, returning when a balancing
 * <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has
 * already occurred, or the thread is interrupted, or, if not
 * absolute and time is not zero, the given time nanoseconds have
 * elapsed, or if absolute, the given deadline in milliseconds
 * since Epoch has passed, or spuriously (i.e., returning for no
 * "reason"). Note: This operation is in the Unsafe class only
 * because <tt>unpark</tt> is, so it would be strange to place it
 * elsewhere.
 */
public native void park(boolean isAbsolute, long time);

native方法:阻塞当前线程,当unpark发生时返回,或者已经发生了unpark时返回(start后但在park之前调用了unpark,或者线程被打断,如果absolute为false且时间不为0,则相对的时间过去会返回,如果absolute为true,则给定的deadline是从纪元来时的时间戳,或虚假唤醒(没有理由的情况下返回)注意!Unsafe类不能直接调用,会报安全异常。

注意LockSupport.park()可能出现虚假唤醒的情况,唤醒后应检查是否满足唤醒条件,如果不满足应继续调用park()。

// LockSupport.java
/**
 * Makes available the permit for the given thread, if it
 * was not already available.  If the thread was blocked on
 * {@code park} then it will unblock.  Otherwise, its next call
 * to {@code park} is guaranteed not to block. This operation
 * is not guaranteed to have any effect at all if the given
 * thread has not been started.
 *
 * @param thread the thread to unpark, or {@code null}, in which case
 *        this operation has no effect
 */
public static void unpark(Thread thread) {
if (thread != null)
    UNSAFE.unpark(thread);
}

调用后如果线程处于阻塞状态则会被唤醒,如果线程不在阻塞状态,则下一次的park调用将不会被阻塞。如果线程没有启动则不保证有效果。

总结:LockSupport.lock(this) 和 LockSupport.unlock(t) 为并发编程提供了一种高效可靠的阻塞和唤醒机制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值