java Callable、Future、FutureTask介面
前言
一般而言,執行緒的建立方法有兩種,一種是繼承Thread類,另一種是實現Runnable介面。
但是這兩種方法都有一個問題:那就是在任務執行完成之後無法獲取返回結果。於是就有了Callable介面,Future介面與FutureTask類的配和取得返回的結果。這也是所謂的“非同步”模型。
Callable 介面介紹
首先回顧一下java.lang.Runnable介面,就聲明瞭run(),其返回值為void,當然就無法獲取結果。
1 @FunctionalInterface 2 public interface Runnable { 3 /** 4 * When an object implementing interface <code>Runnable</code> is used5 * to create a thread, starting the thread causes the object's 6 * <code>run</code> method to be called in that separately executing 7 * thread. 8 * <p> 9 * The general contract of the method <code>run</code> is that it may 10 * take any action whatsoever.11 * 12 * @see java.lang.Thread#run() 13 */ 14 public abstract void run(); 15 }
而Callable的介面如下:
1 @FunctionalInterface 2 public interface Callable<V> { 3 /** 4 * Computes a result, or throws an exception if unable to do so. 5 * 6 * @return computed result7 * @throws Exception if unable to compute a result 8 */ 9 V call() throws Exception; 10 }
該介面聲明瞭一個名稱為call()的方法,同時這個方法可以有返回值V,也可以丟擲異常。
那麼Callable介面是怎麼使用的呢?
無論是Runnable介面的實現類還是Callable介面的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都實現了ExcutorService介面,而因此Callable需要和Executor框架中的ExcutorService結合使用,我們先看看ExecutorService提供的方法:
1 <T> Future<T> submit(Callable<T> task); 2 <T> Future<T> submit(Runnable task, T result); 3 Future<?> submit(Runnable task);
第一個方法:submit提交一個實現Callable介面的任務,並且返回封裝了非同步計算結果的Future。
第二個方法:submit提交一個實現Runnable介面的任務,並且指定了在呼叫Future的get方法時返回的result物件。(不常用)
第三個方法:submit提交一個實現Runnable介面的任務,並且返回封裝了非同步計算結果的Future。
因此我們只要建立好我們的執行緒物件(實現Callable介面或者Runnable介面),然後通過上面3個方法提交給執行緒池去執行即可。還有點要注意的是,除了我們自己實現Callable物件外,我們還可以使用工廠類Executors來把一個Runnable物件包裝成Callable物件。Executors工廠類提供的方法如下:
1 public static Callable<Object> callable(Runnable task) 2 3 public static <T> Callable<T> callable(Runnable task, T result)
測試程式碼
1 public class Task implements Callable { 2 3 @Override 4 public String call() throws Exception { 5 return "call()方法"; 6 } 7 8 public static void main(String[] args) throws ExecutionException, InterruptedException { 9 ExecutorService executorService = Executors.newFixedThreadPool(2); 10 Task task = new Task(); 11 Future<String> submit = executorService.submit(task); 12 System.out.println(submit.get()); 13 } 14 }
輸出結果:call()方法
Future<V> 介面介紹
Future<V>介面是用來獲取非同步計算結果的,說白了就是對具體的Runnable或者Callable物件任務執行的結果進行獲取(get()),取消(cancel()),判斷是否完成等操作。我們看看Future介面的原始碼:
1 public interface Future<V> { 2 boolean cancel(boolean mayInterruptIfRunning); 3 boolean isCancelled(); 4 boolean isDone(); 5 V get() throws InterruptedException, ExecutionException; 6 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; 7 }
方法說明:
V get() :獲取非同步執行的結果,如果沒有結果可用,此方法會阻塞直到非同步計算完成。
V get(Long timeout , TimeUnit unit) :獲取非同步執行結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,如果阻塞時間超過設定的timeout時間,該方法將丟擲異常。
boolean isDone() :如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。
boolean isCancelled() :如果任務完成前被取消,則返回true。
boolean cancel(boolean mayInterruptRunning) :
- 如果任務還沒開始,執行cancel(...)方法將返回false;
- 如果任務已經啟動,執行cancel(true)方法將以中斷執行此任務執行緒的方式來試圖停止任務,如果停止成功,返回true;
- 當任務已經啟動,執行cancel(false)方法將不會對正在執行的任務執行緒產生影響(讓執行緒正常執行到完成),此時返回false;
- 當任務已經完成,執行cancel(...)方法將返回false。
cancel()
注意是試圖取消,並不一定能取消成功。因為任務可能已完成、已取消、或者一些其它因素不能取消,存在取消失敗的可能。boolean型別的返回值是“是否取消成功”的意思。引數mayInterruptRunning表示是否採用中斷的方式取消執行緒執行。
通過方法分析我們也知道實際上Future提供了3種功能:(1)能夠中斷執行中的任務(2)判斷任務是否執行完成(3)獲取任務執行完成後的結果。
測試程式碼
- 使用Callable+Future獲取執行結果
Callable實現類如下:
1 import java.util.concurrent.Callable;
2
3 public class CallableDemo implements Callable<Integer> {
4
5 private int sum;
6 @Override
7 public Integer call() throws Exception {
8 System.out.println("Callable子執行緒開始計算啦!");
9 Thread.sleep(2000);
10
11 for(int i=0 ;i<5000;i++){
12 sum=sum+i;
13 }
14 System.out.println("Callable子執行緒計算結束!");
15 return sum;
16 }
17 }
Callable執行測試類如下:
1 public class CallableTest {
2 public static void main(String[] args) {
3 //建立執行緒池
4 ExecutorService es = Executors.newSingleThreadExecutor();
5 //建立Callable物件任務
6 CallableDemo calTask=new CallableDemo();
7 //提交任務並獲取執行結果
8 Future<Integer> future =es.submit(calTask);
9 //關閉執行緒池
10 es.shutdown();
11 try {
12 Thread.sleep(2000);
13 System.out.println("主執行緒在執行其他任務");
14
15 if(future.get()!=null){
16 //輸出獲取到的結果
17 System.out.println("future.get()-->"+future.get());
18 }else{
19 //輸出獲取到的結果
20 System.out.println("future.get()未獲取到結果");
21 }
22
23 } catch (Exception e) {
24 e.printStackTrace();
25 }
26 System.out.println("主執行緒在執行完成");
27 }
28 }
執行結果:
1 Callable子執行緒開始計算啦!
2 主執行緒在執行其他任務
3 Callable子執行緒計算結束!
4 future.get()-->12497500
5 主執行緒在執行完成
但是我們必須明白Future只是一個介面,我們無法直接建立物件,因此就需要其實現類FutureTask登場啦。
FutureTask類
FutureTask的幾個狀態
1 /** 2 * The run state of this task, initially NEW. The run state 3 * transitions to a terminal state only in methods set, 4 * setException, and cancel. During completion, state may take on 5 * transient values of COMPLETING (while outcome is being set) or 6 * INTERRUPTING (only while interrupting the runner to satisfy a 7 * cancel(true)). Transitions from these intermediate to final 8 * states use cheaper ordered/lazy writes because values are unique 9 * and cannot be further modified. 10 * 11 * Possible state transitions: 12 * NEW -> COMPLETING -> NORMAL 13 * NEW -> COMPLETING -> EXCEPTIONAL 14 * NEW -> CANCELLED 15 * NEW -> INTERRUPTING -> INTERRUPTED 16 */ 17 private volatile int state; 18 private static final int NEW = 0; 19 private static final int COMPLETING = 1; 20 private static final int NORMAL = 2; 21 private static final int EXCEPTIONAL = 3; 22 private static final int CANCELLED = 4; 23 private static final int INTERRUPTING = 5; 24 private static final int INTERRUPTED = 6;
先來看看FutureTask的實現
1 public class FutureTask<V> implements RunnableFuture<V> { ...}
FutureTask類實現了RunnableFuture介面,我們看一下RunnableFuture介面的實現:
1 public interface RunnableFuture<V> extends Runnable, Future<V> { 2 void run(); 3 }
分析:FutureTask除了實現了Future介面外還實現了Runnable介面(既可以通過Runnable介面實現執行緒,也可以通過Future取得執行緒執行完後的結果),因此FutureTask也可以直接提交給Executor執行。
最後我們給出FutureTask的兩種建構函式:
1 public FutureTask(Callable<V> callable) { 2 } 3 4 public FutureTask(Runnable runnable, V result) { 5 }
那FutureTask類有什麼用?為什麼要有一個FutureTask類?
前面說到了Future只是一個介面,而它裡面的cancel,get,isDone等方法要自己實現起來都是非常複雜的。所以JDK提供了一個FutureTask類來供我們使用。
測試程式碼
- 使用Callable+FutureTask獲取執行結果
Callable實現類不變,測試類如下
1 public class CallableTest { 2 public static void main(String[] args) { 3 //建立執行緒池 4 ExecutorService es = Executors.newSingleThreadExecutor(); 5 //建立Callable物件任務 6 CallableDemo calTask=new CallableDemo(); 7 //建立FutureTask 8 FutureTask<Integer> futureTask=new FutureTask<>(calTask); 9 //執行任務 10 es.submit(futureTask); 11 //關閉執行緒池 12 es.shutdown(); 13 try { 14 Thread.sleep(2000); 15 System.out.println("主執行緒在執行其他任務"); 16 17 if(futureTask.get()!=null){ 18 //輸出獲取到的結果 19 System.out.println("futureTask.get()-->"+futureTask.get()); 20 }else{ 21 //輸出獲取到的結果 22 System.out.println("futureTask.get()未獲取到結果"); 23 } 24 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 System.out.println("主執行緒在執行完成"); 29 } 30 }
執行結果:
1 Callable子執行緒開始計算啦! 2 主執行緒在執行其他任務 3 Callable子執行緒計算結束! 4 futureTask.get()-->12497500 5 主執行緒在執行完成