1、基本介紹
Runnable 是一個接口,在它裏面只聲明瞭一個 run()方法,由於 run()方法返回值爲 void 類型,所以在執行完任務之後無法返回任何結果。
Callable 位於 java.util.concurrent 包下,它也是一個接口,在它裏面也只聲明 了一個方法,只不過這個方法叫做 call(),這是一個泛型接口,call()函數返回的類型就是傳遞進來的 V 類型。
Future 就是對於具體的 Runnable 或者 Callable 任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過 get 方法獲取執行結果,該方法會阻塞直到任務返回結果。在Future類中一共有5個方法,如下圖所示。
- cancel(boolean mayInterruptIfRunning) 方法:取消任務,如果任務已經完成、已經被取消或由於某些其他原因而無法取消,則此嘗試將失敗。 如果成功,並且在調用{@code cancel}時此任務尚未開始,則該任務永遠不會運行。 如果任務已經開始,則{@code mayInterruptIfRunning}參數確定是否應中斷執行該任務的線程以嘗試停止該任務。
- get() 方法:獲取返回結果
- get(long timeout, TimeUnit unit) 方法:獲取結果時設置等待時長
- isCancelled() 方法:如果此任務在正常完成之前被取消,則返回{@code true}。
- isDone() 方法:如果此任務完成,則返回{@code true}。
由於Future 只是一個接口,無法直接用來創建對象使用,因此就有了下面的FutureTask。FutureTask 類實現了RunnableFuture 接口,RunnableFuture 繼承了Runnable接口和Future 接口,而FutureTask 實現了RunnableFuture 接口。所以它既可以作爲Runnable被線程執行,又可以作爲Future 得到Callable 的返回值。FutureTask,可取消的異步計算。 此類提供{@link Future}的基本實現,其中包含啓動和取消計算,查詢以查看計算是否完成以及檢索計算結果的方法。 只有在計算完成後才能檢索結果; 如果計算尚未完成,則{@code get}方法將阻塞。 一旦計算完成,就不能重新開始或取消計算(除非使用{@link #runAndReset}調用計算)。UML類圖如下所示,可清晰看到幾個類的關係。
因此我們通過一個線程運行Callable,但是Thread 不支持構造方法中傳遞Callable的實例,所以我們需要通過FutureTask 把一個Callable 包裝成Runnable,然後再通過這個FutureTask 拿到Callable 運行後的返回值。要new 一個FutureTask的實例,有兩種方法,如下圖所示。
2、代碼示例
- 類說明:Future的基本使用
package cn.lspj.ch2.future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 類說明:Future的使用
*/
public class UseFuture {
/**
* 實現Callable接口,允許有返回值
*/
private static class UseCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable 子線程開始計算。。。。。。");
int num = 0;
for(int i=0;i<5000;i++){
num += i;
}
System.out.println("Callable 線程計算結果爲 num =" + num);
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseCallable useCallable = new UseCallable();
// 包裝
FutureTask futureTask = new FutureTask(useCallable);
new Thread(futureTask).start();
System.out.println("獲取子線程Callable計算返回結果:" + futureTask.get());
}
}
執行結果如下:
- 類說明:Future的使用,演示在計算過程中中斷任務
package cn.lspj.ch2.future;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 類說明:Future的使用,演示在計算過程中中斷任務
*/
public class UseFuture {
/**
* 實現Callable接口,允許有返回值
*/
private static class UseCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable 子線程開始計算。。。。。。");
int num = 0;
for(int i=0;i<5000;i++){
if(Thread.currentThread().isInterrupted()){
System.out.println("Callable 子線程計算任務被中斷了。。。。。。。。。。");
return null;
}
Thread.sleep(1);
num += i;
System.out.println("num=" + num);
}
System.out.println("Callable 線程計算結果爲 num =" + num);
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseCallable useCallable = new UseCallable();
// 包裝
FutureTask futureTask = new FutureTask(useCallable);
new Thread(futureTask).start();
Thread.sleep(35);
Random r = new Random(10);
if(r.nextInt() > 5){
System.out.println("獲取子線程Callable計算返回結果:" + futureTask.get());
} else {
System.out.println("cancle..............");
futureTask.cancel(true);
}
}
}
執行結果如下:
從執行結果來看,在任務執行計算的過程中調用task的cancel(boolean mayInterruptIfRunning)方法中斷任務是可以達到中斷任務的目的,但是需要在執行計算的業務邏輯中使用Thread.currentThread().isInterrupted()判斷任務是否中斷,否則計算任務是不會主動中斷的。
備註:博主微信公衆號,不定期更新文章,歡迎掃碼關注。