一、整體說明
通常情況下,我們通過 extendsThread 或者implements Runnable接口來創建線程,線程沒有返回值;
那如何創建有返回值的線程呢?
先來看一個簡單的demo:
// 首先我們創建了一個線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
// futureTask 我們叫做線程任務,構造器的入參是 Callable
FutureTask futureTask = new FutureTask(new Callable<String> () {
@Override
public String call() throws Exception {
Thread.sleep(3000);
// 返回一句話
return "我是子線程"+Thread.currentThread().getName();
}
});
// 把任務提交到線程池中,線程池會分配線程幫我們執行任務
executor.submit(futureTask);
// 得到任務執行的結果
String result = (String) futureTask.get();
log.info("result is {}",result);
在正式學習如何創建有返回值的線程之前,先來看一下關於線程 API 之間關係的依賴圖:
二、各種 API 的底層實現
1 Callable
Callable 是一個接口,約定了線程要做的事情,和 Runnable 一樣,不過這個線程任務是有返回值的,我們來看下接口定義:
public interface Callable<V> {
V call() throws Exception;
}
我們使用的時候,都不會直接使用 Callable,而是會結合 FutureTask 一起使用;
FutureTask 是線程運行的具體任務,從上圖可以看到 FutureTask 實現了 RunnableFuture 接口,而 RunnableFuture 又實現了 Runnable, Future 兩個接口;
2 RunnableFuture
RunnableFuture也是一個接口,最大目的,是讓 Future 對 Runnable 進行管理,可以取消 Runnable,查看 Runnable 是否完成等等;接口定義如下:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
3 Future
Future 定義了各種方法對任務進行了管理,比如說取消任務,得到任務的計算結果等等;
4 FutureTask
現在清楚了,新建線程任務有兩種方式,一種是無返回值的 Runnable,一種是有返回值的 Callable,FutureTask 實現了 RunnableFuture 接口,又集合了 Callable(Callable 是 FutureTask 的屬性),還提供了兩者一系列的轉化方法,這樣 FutureTask 就統一了 Callable 和 Runnable,我們一起看下:
public class FutureTask<V> implements RunnableFuture<V> {}
從源碼中可以看到,FutureTask 有如下重要的屬性:
// 任務狀態
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;//任務被打斷成功
// 組合了 Callable
private Callable<V> callable;
// 異步線程返回的結果
private Object outcome;
// 當前任務所運行的線程
private volatile Thread runner;
// 記錄調用 get 方法時被等待的線程
private volatile WaitNode waiters;
從源碼中可以看到,FutureTask 有兩個構造器,分別接受 Callable 和 Runnable,如下:
// 使用 Callable 進行初始化
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
// 任務狀態初始化
this.state = NEW; // ensure visibility of callable
}
// 使用 Runnable 初始化,並傳入 result 作爲返回結果。
// Runnable 是沒有返回值的,所以 result 一般沒有用,置爲 null 就好了
public FutureTask(Runnable runnable, V result) {
// Executors.callable 方法把 runnable 適配成 RunnableAdapter,RunnableAdapter 實現了 callable,所以也就是把 runnable 直接適配成了 callable。
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
我們注意到入參是 Runnable 的構造器,會使用 Executors.callable 方法來把 Runnnable 轉化成 Callable,Runnnable 和 Callable 兩者都是接口,兩者之間是無法進行轉化的,所以 Java 新建了一個轉化類:RunnableAdapter 來進行轉化,我們來看下轉化的邏輯:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
// 轉化 Runnable 成 Callable 的工具類
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;
}
}
這是一個典型的適配模型,我們要把 Runnable 適配成 Callable,首先要實現 Callable 的接口,接着在 Callable 的 call 方法裏面調用被適配對象(Runnable)的方法。
另外,來看下幾個關鍵方法的實現源碼:
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
// 如果任務已經在執行中了,並且等待一定的時間後,仍然在執行中,直接拋出異常
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
// 任務執行成功,返回執行的結果
return report(s);
}
// 等待任務執行完成
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 計算等待終止時間,如果一直等待的話,終止時間爲 0
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;
}
// 如果正在執行,當前線程讓出 cpu,重新競爭,防止 cpu 飆高
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 如果第一次運行,新建 waitNode,當前線程就是 waitNode 的屬性
else if (q == null)
q = new WaitNode();
// 默認第一次都會執行這裏,執行成功之後,queued 就爲 true,就不會再執行了
// 把當前 waitNode 當做 waiters 鏈表的第一個
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 如果設置了超時時間,並過了超時時間的話,從 waiters 鏈表中刪除當前 wait
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 沒有過超時時間,線程進入 TIMED_WAITING 狀態
LockSupport.parkNanos(this, nanos);
}
// 沒有設置超時時間,進入 WAITING 狀態
else
LockSupport.park(this);
}
}
/**
* run 方法可以直接被調用
* 也可以開啓新的線程進行調用
*/
public void run() {
// 狀態不是任務創建,或者當前任務已經有線程在執行了,直接返回
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
// 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);
}
// 給 outcome 賦值
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
// 取消任務,如果正在運行,嘗試去打斷
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&//任務狀態不是創建 並且不能把 new 狀態置爲取消,直接返回 false
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
// 進行取消操作,打斷可能會拋出異常,選擇 try finally 的結構
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
//狀態設置成已打斷
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
// 清理線程
finishCompletion();
}
return true;
}
FutureTask 是關鍵,通過 FutureTask 把 Runnnable、Callable、Future 都串起來了,使 FutureTask 具有三者的功能,統一了 Runnnable 和 Callable,更方便使用