Java中我想让线程有返回值要怎么办呢(FutureTask原理解析)

943 阅读3分钟

线程的执行单元Runnable没有返回值怎么办

众所周知大名鼎鼎的Runnable是没有返回值的,为了解决这一问题Doug Lea大佬再juc包下面写了一个Callable类

public interface Callable<V> {
    V call() throws Exception;
}

虽然但是,有这玩意也没有用啊是不是,Java创建线程的方式只有Thread,Thread只认Runnable...你这玩意给Thread也没用是不是。

聪明的Doug lea又写了一个类FutureTask,把Callable作为其属性

public class FutureTask<V> implements RunnableFuture<V> {

它继承了RunnableFuture,点进去看

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

发现RunnableFuture继承了Runnable,这就厉害了,这就意味着FutureTask也能被Thread当成可执行的单元了

ok解决了这个问题

FutureTask又是如何获取返回值的呢

先看它的get方法
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    // 点进去
    return report(s);
}
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);
}

发现它当state为NORMAL的时候才会返回outcome,原来返回值在outcome里面啊。

outcome这个值是哪里来的,来看它的run方法

线程start了以后,被执行的会是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 {
                // 执行Callable的call方法
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 设置outcome
        outcome = v;
        // 设置state为NORMAL
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

okk,在这里我们可以看到就是在run方法里面,执行了call方法,然后把返回值设置到outcome里面,再把state设置成NORMAL。是不是就很简单。

get的时候值没准备好怎么办

call这个方法可能执行很长时间,这个时候去get是没有值的

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    // 不是结束状态
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

我们发现如果state如果小于等于COMPLETING,就是执行awaitDone

先看一下state,发现大于COMPLETING,都是已经执行完的状态

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;  // 

ok点进awaitDone方法

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    // 包装成WaitNode加入队列
            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);
    }
}

发现在这里把这个线程封装进了WaitNode的队列,并且挂起了当前线程。

怎么唤醒呢?回到set方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

发现有个finishCompletion,点进去

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
}

好的,所以是在set完值之后如果有等待队列,就是唤醒他们,然后那些线程就可以开心的拿返回值了。

执行call的时候发生了异常怎么办

回到run方法

public void run() {
    // 执行call
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
}

ok在catch里面setException,看看这个方法

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

发现就把异常放进outcome里面,状态设置成EXCEPTIONAL。和正常设置返回值一样,如果有等待队列,也回去唤醒队列中的线程。

等太长时间了我想取消这个任务了

okk有时候任务也会被取消,让我们看看

public boolean cancel(boolean mayInterruptIfRunning) {
    // 就是设置一下state
    // 看一下mayInterruptIfRunning,
    // 如果在运行就改成INTERRUPTING
    // 否则 CANCELLED
    if (!(state == NEW && 
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              // 前面是NEW,这里也可能会变了状态
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        // 返回取消失败
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                // 改成INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        // 唤醒等待队列
        finishCompletion();
    }
    return true;
}

ok看到上面我们知道cancel方法就是把state设置成INTERRUPTED或者是CANCELLED状态,如果设置状态时候值发生已经不是NEW就返回取消失败。如果有等待结果的队列,也会去唤醒一下。