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的實例,有兩種方法,如下圖所示。
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());
}
}
執行結果如下:
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()判斷任務是否中斷,否則計算任務是不會主動中斷的。
更多精彩內容請關注「菜鳥技術棧」微信公眾號
CSDN:
https://blog.csdn.net/lspj201007186/article/details/106247283
往期內容:
JAVA並發編程:線程並發工具類CountDownLatch與CyclicBarrier的作用、應用場景和實戰
JAVA並發編程:線程並發工具類Fork-Join原理分析及實戰
算法:JAVA實現歸併排序
算法:插入排序
並發編程:線程中Join方法的使用示例分析
並發編程:線程基礎、JAVA新啟線程的方式
詳解Java 8 中使用Stream將List轉為Map
MYSQL之limit基本用法