多執行緒 | FutureTask 執行流程
早期文章
在 Java 中可以用來建立執行緒的方式很多,比如由 Java 提供的 Thread、Runnable 等。本文章來介紹使用 FutureTask 建立執行緒,以及其流程。
Thread 和 Runnable 的問題
眾所周知,使用 Thread、Runnable 建立執行緒是非常方便的,只要實現 執行緒的 run 方法即可。但是通過 Thread、Runnable 實現 run 方法建立的執行緒是無法獲取返回結果的,原因是執行緒方法 run 本身是沒有返回值的。但是在很多場景中,我們是需要 非同步執行的同時獲取其執行緒執行的返回結果的。因此 Java 除了 Thread、Runnable 外,還提供了 FutureTask,它使得我們可以在非同步執行的同時獲取到執行緒的返回結果。
本文就來介紹一下 FutureTask 類的簡單使用。
FutureTask 介紹
FutureTask 類本身不能用來建立執行緒,建立執行緒的工作仍然是由 Thread 類來建立的,FutureTask 和 Runnable 類似,是通過 Thread 類的構造方法傳遞給 Thread 類的。但是注意觀察,Thread 類並沒有一個構造方法是用於接受 FutureTask 型別的構造方法。
FutureTask 定義與繼承關係
那麼,FutureTask 為什麼可以傳遞給 Thread 類呢?這裡重點不是看 Thread 類的構造方法,而是應該看一下 FutureTask 類的定義,該類的定義如下:
public class FutureTask<V> implements RunnableFuture<V> {
可以看到,FutureTask 實現了 RunnableFuture 介面,那麼繼續看 RunnableFuture 介面的定義,該定義如下:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
從 RunnableFuture 介面的定義可以看出,它繼承了 Runnable 介面, 那麼這樣,就可以將 FutureTask 類以構造方法引數的形式傳遞給 Thread 類了。 在 RunnableFuture 介面中有一個 run 方法,那麼這就要求實現 RunnableFuture 介面的類要去實現了 run() 方法。這樣,FutureTask 類既然實現了 RunnableFuture 介面,那麼 FutureTask 類中必然有一個 run 方法是供 Thread 類呼叫的。
那麼 RunnableFuture 繼承的 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;
}
Future 是一個泛型介面,通過 get() 方法可以阻塞等待獲取非同步計算的結果,也可以在獲取非同步計算結果的阻塞上設定一個超時時間。還可以通過 cancel() 方法設定讓執行緒取消、使用 isCancelled() 方法判斷執行緒是否被取消、以及通過 isDone() 方法判斷執行緒是否執行完成。
同樣的 ,F uture 介面中的所有實現均在 FutureTask 中可以找到。
總結一下,FutureTask 實現了 Runnable 介面的 run() 方法(該方法在 Thread.start() 後被呼叫),實現了 Future 的 get()、cancel()、isCancelled() 和 isDone() 方法。但是,get() 方法是從何處獲取到執行緒的結果呢?這次來看一下 FutureTask 的 run() 方法吧。
FutureTask 實現的 run 方法分析
FutureTask 實現了 RunnableFuture 介面,RunnableFuture 繼承了 Runnable 介面,那麼 FutureTask 則可以作為 Thread 類的構造方法的引數傳遞給 Thread 類,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 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);
}
}
從上面的程式碼中可以看出,Fut ureTask 的 run() 方法 是固定的, 並不能 在該 run 方法中實現我們自己的業務 程式碼,那應該如何完成自己的執行緒業務程式碼呢?來觀察一下上面的程式碼。
上面的程式碼中,主要看 try 塊中的程式碼,在 第 7 行, 定義了 一個 C allable 的變數 c,在 第 9 行,定義了一個泛型的 result 變數 , 在第 12 行, 有一句 result = c.call(),在第 20 行有一句 set(result) 。我們分別來觀察一下這基礎。
第 7 行程式碼如下:
Callable<V> c = callable;
第 7 行程式碼中的 callable 是 FutureTask 的屬性,其定義如下:
private Callable<V> callable;
它是通過構造方法賦值得到的,FutureTask 的構造方法如下:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
那麼,我們在使用 FutureTask 時,會給它傳遞 Callable<V> 來例項化它。接著看第 12 行程式碼,程式碼如下:
result = c.call()
可以看出, Callable 有一個 call() 方法,並且有返回值,那麼來看一下 Callable<V> 的定義,它的定義如下:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
可以看到它是一個泛型介面,該介面中有一個方法 call(),它有返回值,且可以丟擲異常。而這個 call() 方法,就是我們用來寫自己業務的執行緒方法。然後這個方法在 FutureTask 的 run() 方法中被呼叫。那麼返回值呢?同樣從第 12 行程式碼可以看出,c.call() 後把返回值給到了 result 中,最後呼叫了第 20 行的程式碼,程式碼如下:
set(result)
我們來檢視 set() 方法做了什麼,程式碼如下:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
set() 方法將返回值賦值給了 outcome,outcome 也是 FutureTask 的屬性,其定義如下:
private Object outcome;
前面介紹過,get() 方法是用來獲取返回結果的,我們通過觀察 get() 方法來驗證一下,程式碼如下:
@SuppressWarnings("unchecked")
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);
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
可以看到,get() 方法返回的就是實際的 outcome 這個屬性。
FutureTask 例項
來一個簡單的例子,來看看 FutureTask 的使用,程式碼如下:
public class FutureTaskTest
{
static public class CallableThread implements Callable<Integer> {
/**
* 此處的call方法是實際的執行緒方法,寫我們的非同步任務
* 這裡是一個簡單的計算100的累加和
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i ++) {
sum += i;
}
return sum;
}
}
public static void main(String[] args) {
CallableThread callableThread = new CallableThread();
FutureTask<Integer> integerFutureTask = new FutureTask<>(callableThread);
Thread thread = new Thread(integerFutureTask);
thread.start();
try {
// 這裡通過FutureTask.get()方法來阻塞獲取結算的結果
System.out.println(integerFutureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
上面的示例程式碼非常的簡單,線上程任務中完成一個100內的累加和計算,然後在 main 執行緒中通過 FutureTask 的 get() 方法來阻塞的獲取計算結果。
總結
總結一下,使用 FutureTask 類時,要配合使用 Thread 類和 Callable 介面,剛開始看可能對三個類之間的關係有點亂,但是當實際的瞭解了 FutureTask 類的 run() 方法以後,其實整個脈絡就清楚了。與 FutureTask 類相關的幾個重要介面有 RunnableFuture、Runnable、Future,以及 Callable。能清楚的梳理好它們之間的關係,就可以相對清楚的明白 FutureTask 類了。
希望本文對你有所幫助!喜歡可以點贊、分享!
公眾號內回覆 【mongo】 下載 SpringBoot 整合操作 MongoDB 的文件。
公眾號內回覆 【 cisp知識整理 】 下載 CISP 讀書筆記。
公眾號內 回覆【java開發手冊】獲取《Java開發手冊》黃山版。
- 多執行緒 | FutureTask 執行流程
- Java開發手冊黃山版新增規約摘錄
- 大資料 | HDFS 如何實現故障自動轉移
- ZooKeeper 叢集搭建
- 大資料 | HDFS 元資料持久化筆記
- 大資料 | Java 操作 HDFS 常用 API
- 大資料 | HDFS 常用操作命令
- 給介面新增快取
- 服務註冊、發現和遠端呼叫
- 讓 MongoDB 的 CRUD 有 JPA 的味道
- 使用 MongoTemplate 對 MongoDB 進行 CRUD
- 省記憶體的 Excel 匯入匯出庫還是得了解下它...
- 後端程式設計師的 VUE 超簡單入門筆記
- 後端程式設計師的 ES6 超簡單入門筆記
- 看完即可上手 MyBatis-Plus
- 奇怪的函式呼叫
- 原始套接字打造ping命令
- LeetCode | 735. 行星碰撞
- SpringBoot啟動時執行任務
- Eureka Server開關流程