在上一篇博文中 , 我們瞭解了線程池相關概念, 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
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, 就是阻塞獲取線程執行結果, 這裏主要做兩件事情
- 判斷當前狀態, 如果狀態小於等於 COMPLETING, 表示FutureTask 任務還沒有完結, 所以調用 awaitDone 方法, 讓線程等待.
- 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 介紹 + 一道面試題