1. 程式人生 > 其它 >java Callable、Future、FutureTask介面

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 used
5 * 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 result
7 * @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 主執行緒在執行完成