[Java高併發系列(6)]Java中線程池(2)--Callable和Future

在上一篇博文中 , 我們瞭解了線程池相關概念, ExecutorService 與常用的創建線程池的方法一些參數概念, 大概瞭解了線程池的工作流程. 介紹了三種任務隊列, 四種拒絕方式, 五種線程池模型. 本文將繼續介紹 ExecutorService中相關的類和接口的概念 , 具體來說 , 是Callable 和 Future相關的使用.

1 Callable

1.1 Runnable

關於 Runnable 接口應該是比較熟悉了吧 , 實現其接口即可創建一個線程, run方法中重寫線程要執行的任務…

public interface Runnable {
    public abstract void run();
}

多線程開發中經常使用到Runnable接口, 它定義run方法, 只要對象實現這個方法 , 將對象作爲參數輸入到new Thread(Runnable A ),線程一旦start(),那麼就自動執行了, 沒有任何的返回結果,無法知道什麼時候結束,適用於完全異步的任務,不用關心結果.

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " hello world ");
            }
        } , "thread - 1").start();

1.2 Callable

可以猜到的是, callable是帶返回類型的

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;

Callable定義的接口call(), 它能夠拋出異常 . 並且能夠有一個返回結果。實現了Callable要想提交到線程池中, 直接通過 ExecutorService.submit(new CallAbleTask(i)),但是返回的結果是Future,結果信息從Future裏面取出,具體的業務邏輯在call中執行. 事實上, callable 也確實結合線程池使用的.

2 Future 及其子接口/ 實現類

2.1 Future

進入Future 源碼 可以看到其提供的五個方法

public interface Future<V> {
	boolean cancel(boolean mayInterruptIfRunning);//用來取消任務, 如果取消任務成功則返回true ,如果取消任務失敗則返回false
    boolean isCancelled();//表示任務是否被取消成功, 如果任務正常完成前被取消成功, 返回true
    boolean isDone();// 表示任務是否已經完成, 若任務完成, 返回 true
    V get() throws InterruptedException, ExecutionException;// 用來獲取執行結果, 這個方法會阻塞一哈直到等到任務執行完成才返回
    V get(long timeout, TimeUnit unit)//用來獲取執行結果, 如果在指定時間內, 還沒獲取到結果, 就直接返回null
         throws InterruptedException, ExecutionException, TimeoutException;
}

總的來說, Future能夠控制Callable對象的執行,檢測是否做完, 可以取消它任務, 可以阻塞式獲取結果, 也可以等待一段時間內獲取結果.

即以下三種功能:

  • 判斷任務是否完成
  • 能夠中斷任務
  • 能夠獲取任務執行的結果

2.2 RunnableFuture 接口和它的實現類 FutureTask

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CMvAA7F1-1575335522910)(/home/lowfree/doc/notes/distributed/Future類圖.png)]

RunnableFuture繼承了 Runnable 和 Future, 即相當於在Future接口的基礎上增加了 run() 方法. FutureTask是這個接口的實現類, 由此可通過該FutureTask 來創建線程, 結合Callable使用, 就能夠用FutureTask控制線程執行的任務返回值哦!

看 FutureTask 的構造函數

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

看, 傳入的參數果然是 Callable , 可以推之, 其繼承實現自Future的 get 方法, 是可以把 類型V 返回的 . 除了傳入Callable進行構造, 傳 Runnable 也是可以的, 不過還得帶一個線程執行結果

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

現在我們知道了FutureTask 是Runnable 和 Future 的結合, 如果我們把Runnable 比作是生產者, Future 比作是消費者, 那麼FutureTask 是被這兩者共享的 , 生產者運行run 方法計算結果, 消費者通過get方法獲得結果.

作爲生產者消費者模式 , 其中一個很重要的機制, 就是若生產者數據還沒準備的時候, 消費者會被阻塞. 當生產者數據準備好了以後會喚醒消費者繼續執行.那麼這種像阻塞隊列的機制在FutureTask裏是怎麼實現的呢?

state

private static final int NEW = 0; // NEW 新建狀態,表示這個 FutureTask還沒有開始運行
// COMPLETING 完成狀態, 表示 FutureTask 任務已經計算完畢了
// 但是還有一些後續操作,例如喚醒等待線程操作,還沒有完成。
private static final int COMPLETING = 1;
// FutureTask 任務完結,正常完成,沒有發生異常
private static final int NORMAL = 2;
// FutureTask 任務完結,因爲發生異常。
private static final int EXCEPTIONAL = 3;
// FutureTask 任務完結,因爲取消任務
private static final int CANCELLED = 4;
// FutureTask 任務完結,也是取消任務,不過發起了中斷運行任務線程的中斷請求
private static final int INTERRUPTING = 5;
// FutureTask 任務完結,也是取消任務,已經完成了中斷運行任務線程的中斷請求
private static final int INTERRUPTED = 6;

run方法

  public void run() {
        // 如果狀態 state 不是 NEW,或者設置 runner 值失敗
        // 表示有別的線程在此之前調用 run 方法,併成功設置了 runner 值
        // 保證了只有一個線程可以運行 try 代碼塊中的代碼。
        if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                        null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {	// 只有 c 不爲 null 且狀態 state 爲 NEW 的情況
                V result;
                boolean ran;
                try {
                    result = c.call(); //調用 callable 的 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);
        }
    }

run 方法就是調用callable的 call 方法返回結果值result , 根據是否發生異常嗎調用set(result)或 setExecution(ex) 方法表示FutureTask 任務完結.

get 方法

get方法來自於Future, 就是阻塞獲取線程執行結果, 這裏主要做兩件事情

  1. 判斷當前狀態, 如果狀態小於等於 COMPLETING, 表示FutureTask 任務還沒有完結, 所以調用 awaitDone 方法, 讓線程等待.
  2. report 返回結果值或拋出異常
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 (; ; ) {
            // 如果當前線程中斷標誌位是 true,
            // 那麼從列表中移除節點 q,並拋出 InterruptedException 異常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            int s = state;
            if (s > COMPLETING) { // 當狀態大於 COMPLETING 時,表示 FutureTask 任務已結束。
                if (q != null)
                    q.thread = null; // 將節點 q 線程設置爲 null,因爲線程沒有阻塞等待
                return s;
            }// 表示還有一些後序操作沒有完成,那麼當前線程讓出執行權
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
                //表示狀態是 NEW,那麼就需要將當前線程阻塞等待。
                // 就是將它插入等待線程鏈表中,
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                // 使用 CAS 函數將新節點添加到鏈表中,如果添加失敗,那麼queued 爲 false,
                // 下次循環時,會繼續添加,知道成功。
                queued = UNSAFE.compareAndSwapObject(this,
                        waitersOffset,
                        q.next = waiters, q);
            else if (timed) {// timed 爲 true 表示需要設置超時
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos); // 讓當前線程等待 nanos 時間
            } else
                LockSupport.park(this);
        }
    }
    //被阻塞的線程,會等到 run 方法執行結束之後被喚醒

report 方法

根據傳入的狀態值 s,來決定是拋出異常,還是返回結果值。這個兩種情況都表示 FutureTask 完結了

 private V report(int s) throws ExecutionException {
        Object x = outcome;//表示 call 的返回值
        if (s == NORMAL) // 表示正常完結狀態,所以返回結果值
            return (V) x;
        // 大於或等於 CANCELLED,都表示手動取消 FutureTask 任務,
        // 所以拋出 CancellationException 異常
        if (s >= CANCELLED)
            throw new CancellationException();
        // 否則就是運行過程中,發生了異常,這裏就拋出這個異常
        throw new ExecutionException((Throwable) x);
    }

本文主要介紹了與Runnable 很相似的 Callable , 以及一個Future接口及其他的子接口 RunnableFuture 和 實現類FutureTask, 主要操縱線程任務中的返回值. 在有線程池參與的多線程環境中, 運用FutureTask 操縱各線程執行任務的返回結果是很常見的. 有了這些基礎, 結合上篇文章中講解的一些概念 , 下篇文章即可較詳細介紹 ExecutorService 與各種ThreadPoolExecutor 中的線程池了

[Java高併發系列(5)][詳細]Java中線程池(1)–基本概念介紹

往期回顧

[Java高併發系列(1)]Java 中 synchronized 關鍵字詳解 + 死鎖實例
[Java高併發系列(2)]Java 中 volatile 關鍵字詳解 + volatile 與 sychronized 區別
[Java高併發系列(3)]Java 中 CountDownLatch介紹 + 一道面試題
[Java高併發系列(4)]Java 中 ReentrantLock 介紹 + 一道面試題

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