引入
在上一篇Callable 和 Runnable 的不同?的最后,我們有提到和 Callable 配合的有一個 Future 類,通過 Future 可以了解任務執行情況,或者取消任務的執行,還可獲取任務執行的結果,這些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 強大。
今天我們就來看看這個 Future 是什么。
Future 的作用
Future 最主要的作用是,比如當做一定運算的時候,運算過程可能比較耗時,有時會去查數據庫,或是繁重的計算,比如壓縮、加密等,在這種情況下,如果我們一直在原地等待方法返回,顯然是不明智的,整體程序的運行效率會大大降低。我們可以把運算的過程放到子線程去執行,再通過 Future 去控制子線程執行的計算過程,最后獲取到計算結果。這樣一來就可以把整個程序的運行效率提高,是一種異步的思想。
Callable 和 Future 的關系
接下來我們介紹下 Callable 和 Future 的關系,前面講過,Callable 接口相比于 Runnable 的一大優勢是可以有返回結果,那這個返回結果怎么獲取呢?就可以用 Future 類的 get 方法來獲取 。因此,Future 相當于一個存儲器,它存儲了 Callable 的 call 方法的任務結果。除此之外,我們還可以通過Future 的 isDone 方法來判斷任務是否已經執行完畢了,還可以通過 cancel 方法取消這個任務,或限時獲取任務的結果等,總之 Future 的功能比較豐富。有了這樣一個從宏觀上的概念之后,我們就來具體看一下 Future 類的源碼和使用。
Future源碼
還是老樣子,先看看它的源碼注釋:
A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready. Cancellation is performed by the cancel method. Additional methods are provided to determine if the task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled. If you would like to use a Future for the sake of cancellability but not provide a usable result, you can declare types of the form Future<?> and return null as a result of the underlying task.
Sample Usage (Note that the following classes are all made-up.)interface ArchiveSearcher { String search(String target); } class App {ExecutorService executor = ...ArchiveSearcher searcher = ...void showSearch(final String target)throws InterruptedException {Future<String> future= executor. submit(new Callable<String>() {public String call() {return searcher. search(target);}});displayOtherThings(); // do other things while searchingtry {displayText(future. get()); // use future} catch (ExecutionException ex) {cleanup();return;}} }
The FutureTask class is an implementation of Future that implements Runnable, and so may be executed by an Executor. For example, the above construction with submit could be replaced by:
FutureTask<String> future = new FutureTask<String>(new Callable<String>() { public String call() { return searcher. search(target); }}); executor.execute(future);
Memory consistency effects: Actions taken by the asynchronous computation happen-before actions following the corresponding Future. get() in another thread.
翻譯:
Future 代表異步計算的結果。它提供了一些方法,用于檢查計算是否完成、等待計算完成以及檢索計算結果。只有在計算完成后,才能使用 get 方法檢索結果,如果必要,該方法會阻塞直到結果可用。取消操作通過 cancel 方法執行。還提供了其他方法來判斷任務是正常完成還是被取消。一旦計算完成,就無法再取消該計算。如果你希望為了可取消性而使用 Future,但又不提供可用的結果,可以聲明 Future<?> 形式的類型,并將 null 作為底層任務的結果返回。
示例用法(注意,以下類均為虛構)/*** 定義一個歸檔搜索器接口,包含一個搜索方法。*/ interface ArchiveSearcher {/*** 在歸檔中搜索指定的目標字符串。* * @param target 要搜索的目標字符串* @return 搜索結果*/String search(String target); }/*** 應用程序類,包含執行搜索和顯示結果的方法。*/ class App {// 線程池執行器,使用省略號表示具體實現ExecutorService executor = ...// 歸檔搜索器實例,使用省略號表示具體實現ArchiveSearcher searcher = .../*** 顯示搜索結果的方法。* * @param target 要搜索的目標字符串* @throws InterruptedException 如果在等待搜索結果時線程被中斷*/void showSearch(final String target)throws InterruptedException {// 提交一個 Callable 任務到線程池執行,并返回一個 Future 對象Future<String> future = executor.submit(// 創建一個 Callable 任務,用于執行搜索操作new Callable<String>() {/*** 執行搜索操作并返回結果。* * @return 搜索結果*/public String call() {// 調用搜索器的搜索方法,傳入目標字符串return searcher.search(target);}});// 在搜索進行的同時,顯示其他內容displayOtherThings(); try {// 獲取搜索結果并顯示displayText(future.get()); } catch (ExecutionException ex) {// 處理執行異常,進行清理操作cleanup();return;}} }
FutureTask 類是 Future 的一個實現,它實現了 Runnable 接口,因此可以由執行器(Executor)執行。例如,上述使用 submit 的構建方式可以替換為:
/*** 創建一個 FutureTask 對象,用于異步執行搜索任務。* FutureTask 是一個可取消的異步計算任務,它實現了 RunnableFuture 接口,* 可以作為一個任務提交給 Executor 執行,并可以獲取任務的執行結果。** @param new Callable<String>() { ... } 一個實現了 Callable 接口的匿名類,* 用于定義搜索任務的具體邏輯。* Callable 接口的 call 方法返回一個結果,* 這里的結果是搜索的目標字符串。*/ FutureTask<String> future = new FutureTask<String>(// 定義一個 Callable 任務,該任務會調用 searcher 的 search 方法進行搜索new Callable<String>() {/*** 執行搜索任務并返回結果。** @return 搜索的結果字符串。*/public String call() {// 調用 searcher 的 search 方法,傳入目標字符串進行搜索return searcher. search(target);}}); // 將 FutureTask 提交給 ExecutorService 執行 executor.execute(future);
內存一致性影響:異步計算所執行的操作,在另一個線程中對應 Future.get () 之后的操作之前發生。
對應源碼代碼如下:
/*** 表示異步計算的結果。提供了檢查計算是否完成、等待計算完成以及檢索計算結果的方法。* 只有在計算完成后才能使用{@code get}方法檢索結果,如果需要,會阻塞直到結果準備好。* 可以使用{@code cancel}方法取消計算。還提供了其他方法來確定任務是正常完成還是被取消。* 一旦計算完成,就不能再取消。如果只是為了可取消性而使用{@code Future},* 而不提供可用的結果,可以聲明{@code Future<?>}類型,并讓底層任務返回{@code null}。** @param <V> 此Future的{@code get}方法返回的結果類型*/
public interface Future<V> {/*** 嘗試取消此任務的執行。如果任務已經完成、已經被取消,或者由于其他原因無法取消,* 則此嘗試將失敗。如果成功,并且在調用{@code cancel}時任務尚未開始,* 則此任務不應再運行。如果任務已經開始,則{@code mayInterruptIfRunning}參數* 決定是否應中斷執行此任務的線程以嘗試停止任務。** <p>此方法返回后,后續調用{@link #isDone}將始終返回{@code true}。* 如果此方法返回{@code true},則后續調用{@link #isCancelled}將始終返回{@code true}。** @param mayInterruptIfRunning 如果執行此任務的線程應該被中斷,則為{@code true};* 否則,允許正在進行的任務完成* @return 如果任務無法取消(通常是因為它已經正常完成),則返回{@code false};* 否則返回{@code true}*/boolean cancel(boolean mayInterruptIfRunning);/*** 如果此任務在正常完成之前被取消,則返回{@code true}。** @return 如果此任務在正常完成之前被取消,則返回{@code true}*/boolean isCancelled();/*** 如果此任務已完成,則返回{@code true}。** 完成可能是由于正常終止、異常或取消——在所有這些情況下,此方法都將返回{@code true}。** @return 如果此任務已完成,則返回{@code true}*/boolean isDone();/*** 如有必要,等待計算完成,然后檢索其結果。** @return 計算結果* @throws CancellationException 如果計算被取消* @throws ExecutionException 如果計算拋出異常* @throws InterruptedException 如果當前線程在等待時被中斷*/V get() throws InterruptedException, ExecutionException;/*** 如有必要,最多等待給定的時間以等待計算完成,然后檢索其結果(如果可用)。** @param timeout 最長等待時間* @param unit 超時參數的時間單位* @return 計算結果* @throws CancellationException 如果計算被取消* @throws ExecutionException 如果計算拋出異常* @throws InterruptedException 如果當前線程在等待時被中斷* @throws TimeoutException 如果等待超時*/V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
Future 的方法和用法
可以看到Future 接口的代碼,一共有 5 個方法,其中第 5 個方法是對第 4 個方法的重載,方法名一樣,但是參數不一樣。
get() 方法:獲取結果
get 方法最主要的作用就是獲取任務執行的結果,該方法在執行時的行為取決于 Callable 任務的狀態,可能會發生以下 5 種情況。
- 最常見的就是當執行 get 的時候,任務已經執行完畢了,可以立刻返回,獲取到任務執行的結果。
- 任務還沒有結果,這是有可能的,比如我們往線程池中放一個任務,線程池中可能積壓了很多任務,還沒輪到我去執行的時候,就去 get 了,在這種情況下,相當于任務還沒開始;還有一種情況是任務正在執行中,但是執行過程比較長,所以我去 get 的時候,它依然在執行的過程中。無論是任務還沒開始或在進行中,我們去調用 get 的時候,都會把當前的線程阻塞,直到任務完成再把結果返回回來。
- 任務執行過程中拋出異常,一旦這樣,我們再去調用 get 的時候,就會拋出ExecutionException異常,不管我們執行 call 方法時里面拋出的異常類型是什么,在執行 get 方法時所獲得的異常都是ExecutionException。
- 任務被取消了,如果任務被取消,我們用 get 方法去獲取結果時則會拋出CancellationException。
- 任務超時,我們知道 get 方法有一個重載方法,那就是帶延遲參數的,調用了這個帶延遲參數的get 方法之后,如果 call 方法在規定時間內正常順利完成了任務,那么 get 會正常返回;但是如果到達了指定時間依然沒有完成任務,get 方法則會拋出 TimeoutException,代表超時了。
我們來看一個代碼示例:
/*** OneFuture 類演示了如何使用 Java 的 Future 和 Callable 接口來異步執行任務并獲取結果。* 該類創建一個固定大小的線程池,提交一個 Callable 任務,并等待任務完成以獲取結果。*/
public class OneFuture {/*** 程序的入口點。* 創建一個固定大小的線程池,提交一個 Callable 任務,并打印任務的結果。* 最后關閉線程池。** @param args 命令行參數*/public static void main(String[] args) {// 創建一個固定大小為 10 的線程池ExecutorService service = Executors.newFixedThreadPool(10);// 向線程池提交一個 Callable 任務,并返回一個 Future 對象Future<Integer> future = service.submit(new CallableTask());try {// 等待任務完成并獲取結果System.out.println(future.get());} catch (InterruptedException e) {// 當線程在等待結果時被中斷,打印異常堆棧信息e.printStackTrace();} catch (ExecutionException e) {// 當任務執行過程中拋出異常時,打印異常堆棧信息e.printStackTrace();}// 關閉線程池,不再接受新任務service.shutdown();}/*** CallableTask 類實現了 Callable 接口,用于在一個單獨的線程中執行任務。* 該任務會休眠 3 秒鐘,然后返回一個隨機整數。*/static class CallableTask implements Callable<Integer> {/*** 執行任務的方法。* 線程休眠 3 秒后,返回一個隨機整數。** @return 一個隨機整數* @throws Exception 如果線程在休眠過程中被中斷*/@Overridepublic Integer call() throws Exception {// 線程休眠 3 秒Thread.sleep(3000);// 返回一個隨機整數return new Random().nextInt();}}
}
在這段代碼中,main 方法新建了一個 10 個線程的線程池,并且用 submit 方法把一個任務提交進去。這個任務如代碼的最下方所示,它實現了 Callable 接口,它所做的內容就是先休眠三秒鐘,然后返回一個隨機數。接下來我們就直接把 future.get 結果打印出來,其結果是正常打印出一個隨機數,比如100192 等。這也是 Future 最常用的一種用法。
isDone() 方法:判斷是否執行完畢
下面我們再接著看看 Future 的一些其他方法,比如說 isDone() 方法,該方法是用來判斷當前這個任務是否執行完畢了。
需要注意的是,這個方法如果返回 true 則代表執行完成了;如果返回 false 則代表還沒完成。但這里如果返回 true,并不代表這個任務是成功執行的,比如說任務執行到一半拋出了異常。那么在這種情況下,對于這個 isDone 方法而言,它其實也是會返回 true 的,因為對它來說,雖然有異常發生了,但是這個任務在未來也不會再被執行,它確實已經執行完畢了。所以 isDone 方法在返回 true 的時候,不代表這個任務是成功執行的,只代表它執行完畢了。
我們用一個代碼示例來看一看,代碼如下所示:
/*** GetException 類演示了如何使用 ExecutorService 提交 Callable 任務,并處理任務拋出的異常。*/
public class GetException {/*** main 方法是程序的入口點,創建一個固定大小的線程池,提交一個 Callable 任務,并處理任務執行過程中可能拋出的異常。** @param args 命令行參數*/public static void main(String[] args) {// 創建一個固定大小為 20 的線程池ExecutorService service = Executors.newFixedThreadPool(20);// 向線程池提交一個 Callable 任務,并獲取一個 Future 對象Future<Integer> future = service.submit(new CallableTask());try {// 循環 5 次,每次打印當前循環次數,并休眠 500 毫秒for (int i = 0; i < 5; i++) {System.out.println(i);Thread.sleep(500);}// 打印任務是否完成的狀態System.out.println(future.isDone());// 獲取任務的執行結果,如果任務拋出異常,會在這一步拋出 ExecutionExceptionfuture.get();} catch (InterruptedException e) {// 處理線程被中斷的異常e.printStackTrace();} catch (ExecutionException e) {// 處理任務執行過程中拋出的異常e.printStackTrace();}}/*** CallableTask 類實現了 Callable 接口,用于在一個單獨的線程中執行任務。* 該任務會拋出一個 IllegalArgumentException 異常。*/static class CallableTask implements Callable<Integer> {/*** call 方法是 Callable 接口的實現,該方法會拋出一個 IllegalArgumentException 異常。** @return 由于方法會拋出異常,不會返回任何值* @throws Exception 拋出 IllegalArgumentException 異常*/@Overridepublic Integer call() throws Exception {// 拋出一個 IllegalArgumentException 異常throw new IllegalArgumentException("Callable拋出異常");}}
}
在這段代碼中,可以看到有一個線程池,并且往線程池中去提交任務,這個任務會直接拋出一個異常。那么接下來我們就用一個 for 循環去休眠,同時讓它慢慢打印出 0 ~ 4 這 5 個數字,這樣做的目的是起到了一定的延遲作用。在這個執行完畢之后,再去調用 isDone() 方法,并且把這個結果打印出來,然后再去調用 future.get()。
這段代碼的執行結果是這樣的:
這里要注意,我們知道這個異常實際上是在任務剛被執行的時候就拋出了,因為我們的計算任務中是沒有其他邏輯的,只有拋出異常。我們再來看,控制臺是什么時候打印出異常的呢?它是在 true 打印完畢后才打印出異常信息的,也就是說,在調用 get 方法時打印出的異常。
這段代碼證明了三件事情:
- 第一件事情,即便任務拋出異常,isDone 方法依然會返回 true;
- 第二件事情,雖然拋出的異常是 IllegalArgumentException,但是對于 get 而言,它拋出的異常依然是ExecutionException;
- 第三個事情,雖然在任務執行一開始時就拋出了異常,但是真正要等到我們執行get 的時候,才看到了異常。
cancel 方法:取消任務的執行
下面我們再來看一下 cancel 方法,如果不想執行某個任務了,則可以使用 cancel 方法,會有以下三種情況:
- 第一種情況最簡單,那就是當任務還沒有開始執行時,一旦調用 cancel,這個任務就會被正常取消,未來也不會被執行,那么 cancel 方法返回 true。
- 第二種情況也比較簡單。如果任務已經完成,或者之前已經被取消過了,那么執行 cancel 方法則代表取消失敗,返回 false。因為任務無論是已完成還是已經被取消過了,都不能再被取消了。
- 第三種情況比較特殊,就是這個任務正在執行,這個時候執行 cancel 方法是不會直接取消這個任務的,而是會根據我們傳入的參數做判斷。cancel 方法是必須傳入一個參數,該參數叫作mayInterruptIfRunning,它是什么含義呢?如果傳入的參數是 true,執行任務的線程就會收到一個中斷的信號,正在執行的任務可能會有一些處理中斷的邏輯,進而停止,這個比較好理解。如果傳入的是 false 則就代表不中斷正在運行的任務,也就是說,本次 cancel 不會有任何效果,同時 cancel 方法會返回 false。
那么如何選擇傳入 true 還是 false 呢?
傳入 true 適用的情況是,明確知道這個任務能夠處理中斷。
傳入 false 適用于什么情況呢?
- 如果我們明確知道這個線程不能處理中斷,那應該傳入 false。
- 我們不知道這個任務是否支持取消(是否能響應中斷),因為在大多數情況下代碼是多人協作的,對于這個任務是否支持中斷,我們不一定有十足的把握,那么在這種情況下也應該傳入 false。
- 如果這個任務一旦開始運行,我們就希望它完全的執行完畢。在這種情況下,也應該傳入 false。
這就是傳入 true 和 false 的不同含義和選擇方法。
isCancelled() 方法:判斷是否被取消
最后一個方法是 isCancelled 方法,判斷是否被取消,它和 cancel 方法配合使用,比較簡單。
以上就是關于 Future 的主要方法的介紹了。
用 FutureTask 來創建 Future
除了用線程池的 submit 方法會返回一個 future 對象之外,同樣還可以用 FutureTask 來獲取 Future 類和任務的結果。
FutureTask 首先是一個任務(Task),然后具有 Future 接口的語義,因為它可以在將來(Future)得到執行的結果。
我們來看一下 FutureTask 的源碼注釋:
A cancellable asynchronous computation. This class provides a base implementation of Future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; the get methods will block if the computation has not yet completed. Once the computation has completed, the computation cannot be restarted or cancelled (unless the computation is invoked using runAndReset).
A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution.
In addition to serving as a standalone class, this class provides protected functionality that may be useful when creating customized task classes.翻譯:
一個可取消的異步計算。此類提供了Future的基本實現,包含啟動和取消計算、查詢計算是否完成以及檢索計算結果的方法。只有在計算完成后才能檢索結果;如果計算尚未完成,get方法將阻塞。一旦計算完成,除非使用runAndReset調用計算,否則無法重新啟動或取消該計算。
FutureTask可用于包裝Callable或Runnable對象。由于FutureTask實現了Runnable接口,因此FutureTask可以提交給Executor執行。
除了作為一個獨立的類,此類還提供了一些受保護的功能,在創建自定義任務類時可能會很有用。
其代碼實現:
public class FutureTask<V> implements RunnableFuture<V> {
... ...
}
可以看到,它實現了一個接口,這個接口叫作 RunnableFuture。我們再來看一下 RunnableFuture 接口的代碼實現:
/*** 一個繼承了 {@link Runnable} 和 {@link Future} 接口的接口。* 它表示一個可運行的未來任務,當成功執行 {@code run} 方法時,* 會導致這個 {@code Future} 任務完成,并且可以通過它的方法獲取執行結果。** @param <V> 此 Future 的 {@code get} 方法返回的結果類型*/
public interface RunnableFuture<V> extends Runnable, Future<V> {/*** 執行此 Future 任務的計算。* 如果任務未被取消,則將此 Future 設置為其計算結果。* 該方法繼承自 {@link Runnable} 接口,用于定義任務的執行邏輯。*/void run();
}
可以看出,它是 extends Runnable 和 Future 這兩個接口的。
既然 RunnableFuture 繼承了 Runnable 接口和 Future 接口,而 FutureTask 又實現了RunnableFuture 接口,所以 FutureTask 既可以作為 Runnable 被線程執行,又可以作為 Future 得到Callable 的返回值。
典型用法是,把 Callable 實例當作 FutureTask 構造函數的參數,生成 FutureTask 的對象,然后把這個對象當作一個 Runnable 對象,放到線程池中或另起線程去執行,最后還可以通過 FutureTask 獲取任務執行的結果。
下面我們就用代碼來演示一下:
/*** 該類演示了如何使用 FutureTask 來執行一個異步任務,并獲取任務的執行結果。*/
public class FutureTaskDemo {/*** 程序的入口點,創建并啟動一個 FutureTask 來執行異步任務,并打印任務的執行結果。** @param args 命令行參數*/public static void main(String[] args) {// 創建一個 Task 實例,該實例實現了 Callable 接口Task task = new Task();// 創建一個 FutureTask 實例,用于包裝 Task 實例FutureTask<Integer> integerFutureTask = new FutureTask<>(task);// 創建一個新的線程,并將 FutureTask 作為任務提交給該線程執行new Thread(integerFutureTask).start();try {// 調用 FutureTask 的 get() 方法獲取任務的執行結果,并打印輸出System.out.println("task運行結果:"+integerFutureTask.get());} catch (InterruptedException e) {// 如果線程在等待結果時被中斷,打印異常堆棧信息e.printStackTrace();} catch (ExecutionException e) {// 如果任務執行過程中拋出異常,打印異常堆棧信息e.printStackTrace();}}
}/*** 該類實現了 Callable 接口,用于定義一個可執行的任務。*/
class Task implements Callable<Integer> {/*** 該方法是 Callable 接口的實現,定義了任務的具體邏輯。** @return 任務執行的結果* @throws Exception 如果任務執行過程中發生異常*/@Overridepublic Integer call() throws Exception {// 打印信息,表示子線程正在計算System.out.println("子線程正在計算");// 初始化一個變量 sum 用于存儲累加結果int sum = 0;// 使用 for 循環計算 0 到 99 的整數之和for (int i = 0; i < 100; i++) {sum += i;}// 返回計算結果return sum;}
}
在這段代碼中可以看出,首先創建了一個實現了 Callable 接口的 Task,然后把這個 Task 實例傳入到FutureTask 的構造函數中去,創建了一個 FutureTask 實例,并且把這個實例當作一個 Runnable 放到 new Thread() 中去執行,最后再用 FutureTask 的 get 得到結果,并打印出來。
執行結果是 4950,正是任務里 0+1+2+...+99 的結果。