Java异步编程的核心类:FutureTask

既然要说异步编程,肯定首先得知道啥是异步、啥是同步,我在文章同步和异步、阻塞和非阻塞的区别中已经讲过了,此处就不再赘述了。

一、FutureTask

1、FutureTask的通俗解释

FutureTask的通俗解释 有一天你饿了,想吃饭,但是你不会做,这时候你就跟你妈说:妈,我饿了,我要吃饭。然后你妈就去厨房做饭,同时你就去看球赛了(但是你不想被打扰,就跟你妈说,饭做好了贴个墙纸就好了)。你妈把饭做好并端到餐桌上了,并贴了个墙纸,也不告诉你,然后你饿的受不了了,看了一下墙,已经有贴纸了,你就去餐桌上吃饭了,如果没有贴纸你就继续看球赛。FutureTask就是这张墙纸

当然,你要真这么干,估计你妈会打死你

一句话总结FutureTask:FutureTask也即代表了异步执行的任务 FutureTask也即代表了异步执行的任务

2、FutureTask标准用法

public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    Future<?> future = executorService.submit(() -> {
        System.out.println("start");
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    });

    // 判断是否完成
    System.out.println(future.isDone());

    // 无限期等待
    // future.get();

    future.get(2, TimeUnit.SECONDS);
    if (!future.isDone()) {
        // cancel取消任务执行,参数为True:任务执行了但没完成,中断它
        future.cancel(true);
    }
}
复制代码

3、FutureTask源码

public class FutureTask<V> implements RunnableFuture<V> {
}
复制代码
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
复制代码
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;

// 由于FutureTask实现了RunnableFuture,RunnableFuture继承了Runnable,而线程池
// 只执行Runnable的run方法,所以run方法是FutureTask放入线程池执行的第一个方法
public void run() {
    // 状态必须是NEW(新建状态),且成功的将其runner修改为当前线程,代表了
    // FutureTask就保留了当前正在执行它的线程,所以我们就可以通过runner对象
    // 在cancel中中断线程
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        // 查看内部执行的执行体,也就是用户定义的函数Callable是否为空,且当前
        // 状态是否已经被改变,也就是执行前,状态必须是NEW新建状态。
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 执行用户定义函数
                result = c.call();
                // 执行成功
                ran = true;
            } catch (Throwable ex) {
                // 捕捉用户定义的函数,执行异常的结果
                result = null;
                // 标志执行失败
                ran = false;
                // 设置执行失败的结果,outcome
                setException(ex);
            }
            if (ran)
                // 如果执行成功。设置正常执行结果outcome 
                set(result);
        }
    } finally {
        // 执行完毕后,不持有thread的引用,help GC
        runner = null;
        // 判断状态是否被中断,如果是中断处理,那么调用
        // handlePossibleCancellationInterrupt处理中断
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
复制代码

(1)setException(执行异常)

// 当任务执行时发生了异常,调用该方法,将FutureTask变为异常完成状态
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        // 不保证可见性,但是保证不会发生重排序,即不会重排序到上一行代码前
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}
复制代码
// 完成整个FutureTask的执行
private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        // 如果有别的线程等待当前任务完成,那么将他们唤醒。算法:单向链表遍历
        // 
        // 通过CAS操作,保证了可见性,而上面的putOrderedInt保证了顺序写入。
        // (站在JMM模型上是对的,站在CPU底层这是错的)
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                // 等待线程不为空,那么唤醒
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    // 调用unpark唤醒
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                // 断开保留下一个任务的链,帮助GC
                q.next = null;
                q = next;
            }
            break;
        }
    }

    // 此时,所有等待任务完成的线程全部都已经唤醒了。回调子类的钩子函数。
    done();
    // 任务完成了,必定不需要持有执行体了,所以置空
    callable = null;        // to reduce footprint
}
复制代码

(2)set(执行成功)

// 线程执行体正常执行完成,那么调用该方法设置FutureTask正常完成
// (参考setException的实现,一模一样,只是最终状态不一样而已)
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }    
}
复制代码

(3)cancel

FutureTask中还有一个很重要的方法——cancel,用于取消任务执行。

// 外部线程可以通过调用该方法,取消任务的执行
// mayInterruptIfRunning指明是否中断调用线程
// 此时任务可能处于的状态:1、没有被执行 2、正在执行中 3、已经执行完成
public boolean cancel(boolean mayInterruptIfRunning) {
    // 如果当前任务已经完成,返回false,此时可能正在完成中,可能已经完成
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    
        // 如果指定了中断线程
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    // 调用interrupt中断
                    t.interrupt();
            } finally { // 最终将状态置为中断
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        // 最终还是调用finishCompletion完成任务并唤醒等待线程
        finishCompletion();
    }
    return true;
}
复制代码

注意:FutureTask如果不是在线程池中运行,那么调用cancel方法可能会导致业务线程在后面执行业务的时候响应了不属于它自己的中断,因为这个中断是被调用线程由于标志FutureTask线程把任务执行完毕,而不是用于中断业务的执行。

(4)handlePossibleCancellationInterrupt

在FutureTask的源码中Run方法里,finally中会调用handlePossibleCancellationInterrupt处理中断

// 当FutureTask正在被线程执行的时候,那么别的线程调用Cancel方法且指定了中断
// 线程时,将会调用该方法
private void handlePossibleCancellationInterrupt(int s) {
    // 如果快照状态为INTERRUPTING,那么循环等待它变为INTERRUPTED
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            // 使用线程让步来等待,让出当前线程的CPU资源
            Thread.yield(); // wait out pending interrupt

    // assert state == INTERRUPTED;

    // We want to clear any interrupt we may have received from
    // cancel(true).  However, it is permissible to use interrupts
    // as an independent mechanism for a task to communicate with
    // its caller, and there is no way to clear only the
    // cancellation interrupt.
    //
    // Thread.interrupted();
}
复制代码

おすすめ

転載: juejin.im/post/7050463155310297118
おすすめ