Java多線程編程:Callable、Future和FutureTask淺析

咱們知道建立線程的方式有兩種,一種是實現Runnable接口,另外一種是繼承Thread,可是這兩種方式都有個缺點,那就是在任務執行完成以後沒法獲取返回結果,那若是咱們想要獲取返回結果該如何實現呢?html

是的,從JAVA SE 5.0開始引入了Callable和Future,經過它們構建的線程,在任務執行完成後就能夠獲取執行結果,今天咱們就來聊聊線程建立的第三種方式,那就是實現Callable接口。java

 

1.Callable<V>接口編程

咱們先回顧一下java.lang.Runnable接口,就聲明瞭run(),其返回值爲void,固然就沒法獲取結果了。併發

[java] view plain copyapp

 print?框架

  1. public interface Runnable {  
  2.     public abstract void run();  
  3. }  

而Callable的接口定義以下異步

[java] view plain copyide

 print?函數

  1. public interface Callable<V> {   
  2.       V   call()   throws Exception;   
  3. }   

該接口聲明瞭一個名稱爲call()的方法,同時這個方法能夠有返回值V,也能夠拋出異常。嗯,對該接口咱們先了解這麼多就行,下面咱們來講明如何使用,前篇文章咱們說過,不管是Runnable接口的實現類仍是Callable接口的實現類,均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都實現了ExcutorService接口,而所以Callable須要和Executor框架中的ExcutorService結合使用,咱們先看看ExecutorService提供的方法:測試

[java] view plain copy

 print?

  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工廠類提供的方法以下:

[java] view plain copy

 print?

  1. public static Callable<Object> callable(Runnable task)  
  2. public static <T> Callable<T> callable(Runnable task, T result)  

2.Future<V>接口

Future<V>接口是用來獲取異步計算結果的,說白了就是對具體的Runnable或者Callable對象任務執行的結果進行獲取(get()),取消(cancel()),判斷是否完成等操做。咱們看看Future接口的源碼:

[java] view plain copy

 print?

  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 isCanceller() :若是任務完成前被取消,則返回true。

boolean cancel(boolean mayInterruptRunning) :若是任務還沒開始,執行cancel(...)方法將返回false;若是任務已經啓動,執行cancel(true)方法將以中斷執行此任務線程的方式來試圖中止任務,若是中止成功,返回true;當任務已經啓動,執行cancel(false)方法將不會對正在執行的任務線程產生影響(讓線程正常執行到完成),此時返回false;當任務已經完成,執行cancel(...)方法將返回false。mayInterruptRunning參數表示是否中斷執行中的線程。

 

經過方法分析咱們也知道實際上Future提供了3種功能:(1)可以中斷執行中的任務(2)判斷任務是否執行完成(3)獲取任務執行完成後額結果。

可是咱們必須明白Future只是一個接口,咱們沒法直接建立對象,所以就須要其實現類FutureTask登場啦。

 

 

3.FutureTask類

咱們先來看看FutureTask的實現

[java] view plain copy

 print?

  1. public class FutureTask<V> implements RunnableFuture<V> {  

FutureTask類實現了RunnableFuture接口,咱們看一下RunnableFuture接口的實現:

[java] view plain copy

 print?

  1. public interface RunnableFuture<V> extends Runnable, Future<V> {  
  2.     void run();  
  3. }  

分析:FutureTask除了實現了Future接口外還實現了Runnable接口,所以FutureTask也能夠直接提交給Executor執行。 固然也能夠調用線程直接執行(FutureTask.run())。接下來咱們根據FutureTask.run()的執行時機來分析其所處的3種狀態:

(1)未啓動,FutureTask.run()方法尚未被執行以前,FutureTask處於未啓動狀態,當建立一個FutureTask,並且沒有執行FutureTask.run()方法前,這個FutureTask也處於未啓動狀態。

(2)已啓動,FutureTask.run()被執行的過程當中,FutureTask處於已啓動狀態。

(3)已完成,FutureTask.run()方法執行完正常結束,或者被取消或者拋出異常而結束,FutureTask都處於完成狀態。


下面咱們再來看看FutureTask的方法執行示意圖(方法和Future接口基本是同樣的,這裏就不過多描述了)

分析:

(1)當FutureTask處於未啓動或已啓動狀態時,若是此時咱們執行FutureTask.get()方法將致使調用線程阻塞;當FutureTask處於已完成狀態時,執行FutureTask.get()方法將致使調用線程當即返回結果或者拋出異常。

(2)當FutureTask處於未啓動狀態時,執行FutureTask.cancel()方法將致使此任務永遠不會執行。

當FutureTask處於已啓動狀態時,執行cancel(true)方法將以中斷執行此任務線程的方式來試圖中止任務,若是任務取消成功,cancel(...)返回true;但若是執行cancel(false)方法將不會對正在執行的任務線程產生影響(讓線程正常執行到完成),此時cancel(...)返回false。

當任務已經完成,執行cancel(...)方法將返回false。

最後咱們給出FutureTask的兩種構造函數:

[java] view plain copy

 print?

  1. public FutureTask(Callable<V> callable) {  
  2. }  
  3. public FutureTask(Runnable runnable, V result) {  
  4. }  

3.Callable<V>/Future<V>/FutureTask的使用

經過上面的介紹,咱們對Callable,Future,FutureTask都有了比較清晰的瞭解了,那麼它們到底有什麼用呢?咱們前面說過經過這樣的方式去建立線程的話,最大的好處就是可以返回結果,加入有這樣的場景,咱們如今須要計算一個數據,而這個數據的計算比較耗時,而咱們後面的程序也要用到這個數據結果,那麼這個時Callable豈不是最好的選擇?咱們能夠開設一個線程去執行計算,而主線程繼續作其餘事,然後面須要使用到這個數據時,咱們再使用Future獲取不就能夠了嗎?下面咱們就來編寫一個這樣的實例

3.1 使用Callable+Future獲取執行結果

Callable實現類以下:

[java] view plain copy

 print?

  1. package com.zejian.Executor;  
  2. import java.util.concurrent.Callable;  
  3. /** 
  4.  * @author zejian 
  5.  * @time 2016年3月15日 下午2:02:42 
  6.  * @decrition Callable接口實例 
  7.  */  
  8. public class CallableDemo implements Callable<Integer> {  
  9.       
  10.     private int sum;  
  11.     @Override  
  12.     public Integer call() throws Exception {  
  13.         System.out.println("Callable子線程開始計算啦!");  
  14.         Thread.sleep(2000);  
  15.           
  16.         for(int i=0 ;i<5000;i++){  
  17.             sum=sum+i;  
  18.         }  
  19.         System.out.println("Callable子線程計算結束!");  
  20.         return sum;  
  21.     }  
  22. }  

Callable執行測試類以下:

 

 

[java] view plain copy

 print?

  1. package com.zejian.Executor;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.Future;  
  5. /** 
  6.  * @author zejian 
  7.  * @time 2016年3月15日 下午2:05:43 
  8.  * @decrition callable執行測試類 
  9.  */  
  10. public class CallableTest {  
  11.       
  12.     public static void main(String[] args) {  
  13.         //建立線程池  
  14.         ExecutorService es = Executors.newSingleThreadExecutor();  
  15.         //建立Callable對象任務  
  16.         CallableDemo calTask=new CallableDemo();  
  17.         //提交任務並獲取執行結果  
  18.         Future<Integer> future =es.submit(calTask);  
  19.         //關閉線程池  
  20.         es.shutdown();  
  21.         try {  
  22.             Thread.sleep(2000);  
  23.         System.out.println("主線程在執行其餘任務");  
  24.           
  25.         if(future.get()!=null){  
  26.             //輸出獲取到的結果  
  27.             System.out.println("future.get()-->"+future.get());  
  28.         }else{  
  29.             //輸出獲取到的結果  
  30.             System.out.println("future.get()未獲取到結果");  
  31.         }  
  32.           
  33.         } catch (Exception e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.         System.out.println("主線程在執行完成");  
  37.     }  
  38. }  

 

執行結果:

Callable子線程開始計算啦!

主線程在執行其餘任務

Callable子線程計算結束!

future.get()-->12497500

主線程在執行完成

3.2 使用Callable+FutureTask獲取執行結果

[java] view plain copy

 print?

  1. package com.zejian.Executor;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.Future;  
  5. import java.util.concurrent.FutureTask;  
  6. /** 
  7.  * @author zejian 
  8.  * @time 2016年3月15日 下午2:05:43 
  9.  * @decrition callable執行測試類 
  10.  */  
  11. public class CallableTest {  
  12.       
  13.     public static void main(String[] args) {  
  14. //      //建立線程池  
  15. //      ExecutorService es = Executors.newSingleThreadExecutor();  
  16. //      //建立Callable對象任務  
  17. //      CallableDemo calTask=new CallableDemo();  
  18. //      //提交任務並獲取執行結果  
  19. //      Future<Integer> future =es.submit(calTask);  
  20. //      //關閉線程池  
  21. //      es.shutdown();  
  22.           
  23.         //建立線程池  
  24.         ExecutorService es = Executors.newSingleThreadExecutor();  
  25.         //建立Callable對象任務  
  26.         CallableDemo calTask=new CallableDemo();  
  27.         //建立FutureTask  
  28.         FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
  29.         //執行任務  
  30.         es.submit(futureTask);  
  31.         //關閉線程池  
  32.         es.shutdown();  
  33.         try {  
  34.             Thread.sleep(2000);  
  35.         System.out.println("主線程在執行其餘任務");  
  36.           
  37.         if(futureTask.get()!=null){  
  38.             //輸出獲取到的結果  
  39.             System.out.println("futureTask.get()-->"+futureTask.get());  
  40.         }else{  
  41.             //輸出獲取到的結果  
  42.             System.out.println("futureTask.get()未獲取到結果");  
  43.         }  
  44.           
  45.         } catch (Exception e) {  
  46.             e.printStackTrace();  
  47.         }  
  48.         System.out.println("主線程在執行完成");  
  49.     }  
  50. }  

執行結果:

Callable子線程開始計算啦!

主線程在執行其餘任務

Callable子線程計算結束!

futureTask.get()-->12497500

主線程在執行完成

 

主要參考資料:

java併發編程的藝術

相關文章:http://www.cnblogs.com/dolphin0520/p/3949310.html

相關文章
相關標籤/搜索