Runnable、Callable和Future

  我們通常說,在Java裏面起多線程有兩種方法:直接繼承Thread類或者是實現Runnable接口。但是這兩種方法都不能有直接的返回值,因爲run方法是void類型,也沒有提供其他的可以返回某類型的方法,我們可以通過線程間通信或者修改共享變量的方法來獲取線程執行後的結果。
  要直接實現返回變量類型,我們可以使用Callable接口

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

  該接口只聲明瞭一個方法,可以返回一個帶有泛型的類型V。在使用是可以將任務寫在call裏面,並返回相應的值。
  這種方式的線程必須採用ExecutorService接口中的submit方法提交任務,並且將返回一個Future<V>,用於獲取最終的結果。

  Future提供一個泛型接口

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

  除了get用於獲取線程執行的結果外,還提供了控制線程的方法cancel以及查詢線程工作狀態的方法等。從中可以看出,實現時應該是和具體線程緊耦合的。

  上面是應用層面上的幾個點。下面我們想要搞清楚幾個問題:Runnable和Callable本質上到底有哪些區別;它們起線程的方式爲什麼不一樣,爲什麼Callable不能像Runnable那樣通過Thread類來起一個線程;還有Future是怎麼獲取線程運行的結果的。

1. Runnable和Callable

  Runnable和Callable都是兩個接口,其中前者只聲明瞭一個void run方法,後者聲明瞭一個V call方法。我們將任務寫在run或者call中。僅僅從這裏還看不出有何聯繫,只能說相似。
  通過Runnable起線程時,我們都是通過將Runnable傳給一個Thread的構造器,最終通過Thread類來起線程的:

    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }

  這是Thread構造器中完整的一個版本,其實是調用了一個init方法:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    ...
    }

  實現代碼省略了,其中target是我們傳進來的一個Runnable的實現,將賦值給Thread類裏面的一個內部的Runnable target。以上簡而言之就是完成了一個開線程的初始化工作,比如配置線程棧空間大小等。
  起線程時我們還要調用Thread類中的start方法,這其實是起線程的核心:

    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

  可以看到,其中最核心的就是調用了一個start0()方法,這是一個native的方法,也就是用c或者c++實現的部分,我們看不到源碼,我想裏面應該會調用target的run方法。

  Thread類裏面的run方法:

    public void run() {
        if (target != null) {
            target.run();
        }
    }

  從這裏我們可以推知,start0()調用Thread的run方法,實際上是調用的我們傳入的Runnable的run方法。
  在採用直接繼承Thread的時候,因爲我們覆蓋了run方法,所以調用的是我們寫的run方法。所以可以看到,這兩種方式的調用本質上一樣的。

  • 那Callable起線程的方式和上述方式有何區別呢
    因爲Callable起線程要依賴於ExecutorService接口的submit方法,AbstractExecutorService實現了ExecutorService的上述方法:
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

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

  三種形式我們可以看到,不管傳入的參數是Runnable還是Callable,都會被包裝成一個RunnableFuture,這是一個接口,public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>,注意這裏接口允許多重繼承。
  現在我們需要關注的是,對於Runnable和Callable分別是如何被包裝成RunnableFuture的,因爲此後他們被execute調用的方式都一樣,execute都將他們當作一個Runnable來看待。

  我們來看FutureTask構造器的不同重載版本:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

  我們看到,在FutureTask裏面,我們最終都是構造了一個Callable類型的成員變量callable,對於Callable的參數直接賦值就好了,對於Runnable類型的參數,我們怎麼辦呢,答案是通過Executors.callable方法得到一個相應的Callable,怎麼實現的呢,我們可以想到可以使用適配器。看源碼:

//class Executors
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

  下面的是一個RunnableAdapter的實現,也就是將Runnable的run方法包裝到Callable的call裏面去了,因爲兩者的行爲功能是一致的,都是完成用戶的任務。

  至此我們總結一下,Callable在submit中是被包裝成一個RunnableFuture,然後再提交給execute執行的。execute將傳入的參數看成是一個Runnable,將會調用其中的run方法,在FutureTask中的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 {
                    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);
        }
    }

  這裏我們也看到,FutureTask作爲一個Runnable,其核心實現的run方法最終是調用了其成員變量callable的call方法,而這個callable成員變量是通過FutureTask的構造器傳進來的,可能是Runnable也可能是Callable,如果是Runnable還要適配成Callable。

  通過以上介紹,應該能夠比較清楚的瞭解了Callable的執行邏輯,還有Runnable採用ExecutorService.submit方式執行的邏輯了。

1. 關於Future

  Future可能是和線程耦合的,我們想來了解一下Future和線程耦合的一些細節:
它是一個接口,在jdk中的唯一實現就是前面提到的FutureTask,我們來看Future的主要方法在FutureTask中的實現:

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

  這是阻塞讀取結果的方法實現,可以看到調用了awaitDone方法:

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

  具體細節還看不太懂,大概就是不斷地去查詢是否執行完畢,此外,我們還想要了解這個Future是怎麼得知線程執行的結果的,看返回值那句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);
    }

  就是直接返回一個成員變量outcome,我們看是哪裏將結果賦給outcome的:

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

  是這裏的set方法,再看哪裏調用了set方法,發現是在FutureTask中的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 {
                    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);
        }
    }

  總結一下,也就是說FutureTask裏面的實現是這樣的:get方法查詢線程執行狀態,當得知運行完畢後直接去一個outcome變量取結果(report()方法)。這裏的結果是run方法在運行到後面set進去的。也就是說Future的get方式得以實現是因爲FutureTask本身就是一個Future也是Runnable,所以可以控制、獲知線程的執行狀態,必要是可以獲取運行的結果。
以上。

發佈了59 篇原創文章 · 獲贊 9 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章