天天看點

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

java多線程-概念&建立啟動&中斷&守護線程&優先級&線程狀态( 多線程程式設計之一)

java多線程同步以及線程間通信詳解&消費者生産者模式&死鎖&Thread.join()( 多線程程式設計之二)

java&android線程池-Executor架構之ThreadPoolExcutor&ScheduledThreadPoolExecutor淺析(多線程程式設計之三)

Java多線程:Callable、Future和FutureTask淺析(多線程程式設計之四)

通過前面幾篇的學習,我們知道建立線程的方式有兩種,一種是實作Runnable接口,另一種是繼承Thread,但是這兩種方式都有個缺點,那就是在任務執行完成之後無法擷取傳回結果,那如果我們想要擷取傳回結果該如何實作呢?還記上一篇Executor架構結構中提到的Callable接口和Future接口嗎?,是的,從JAVA SE 5.0開始引入了Callable和Future,通過它們建構的線程,在任務執行完成後就可以擷取執行結果,今天我們就來聊聊線程建立的第三種方式,那就是實作Callable接口。

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

1.Callable<V>接口 我們先回顧一下java.lang.Runnable接口,就聲明了run(),其傳回值為void,當然就無法擷取結果了。

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

而Callable的接口定義如下

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

該接口聲明了一個名稱為call()的方法,同時這個方法可以有傳回值V,也可以抛出異常。嗯,對該接口我們先了解這麼多就行,下面我們來說明如何使用, 前篇文章我們說過,無論是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. public static <T> Callable<T> callable (Runnable task, T result)

2.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 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的實作

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接口,是以FutureTask也可以直接送出給Executor執行。 當然也可以調用線程直接執行(FutureTask.run())。接下來我們根據FutureTask.run()的執行時機來分析其所處的3種狀态: (1)未啟動,FutureTask.run()方法還沒有被執行之前,FutureTask處于未啟動狀态,當建立一個FutureTask,而且沒有執行FutureTask.run()方法前,這個FutureTask也處于未啟動狀态。 (2)已啟動,FutureTask.run()被執行的過程中,FutureTask處于已啟動狀态。 (3)已完成,FutureTask.run()方法執行完正常結束,或者被取消或者抛出異常而結束,FutureTask都處于完成狀态。

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

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

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

分析: (1)當FutureTask處于未啟動或已啟動狀态時,如果此時我們執行FutureTask.get()方法将導緻調用線程阻塞;當FutureTask處于已完成狀态時,執行FutureTask.get()方法将導緻調用線程立即傳回結果或者抛出異常。 (2)當FutureTask處于未啟動狀态時,執行FutureTask.cancel()方法将導緻此任務永遠不會執行。 當FutureTask處于已啟動狀态時,執行cancel(true)方法将以中斷執行此任務線程的方式來試圖停止任務,如果任務取消成功,cancel(...)傳回true;但如果執行cancel(false)方法将不會對正在執行的任務線程産生影響(讓線程正常執行到完成),此時cancel(...)傳回false。 當任務已經完成,執行cancel(...)方法将傳回false。 最後我們給出FutureTask的兩種構造函數:

  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實作類如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.Callable;
  3. public class CallableDemo implements Callable<Integer> {
  4. private int sum;
  5. @Override
  6. public Integer call() throws Exception {
  7. System.out.println( "Callable子線程開始計算啦!");
  8. Thread.sleep( );
  9. for( int i= ;i< ;i++){
  10. sum=sum+i;
  11. }
  12. System.out.println( "Callable子線程計算結束!");
  13. return sum;
  14. }
  15. }

Callable執行測試類如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. public class CallableTest {
  6. public static void main(String[] args) {
  7. //建立線程池
  8. ExecutorService es = Executors.newSingleThreadExecutor();
  9. //建立Callable對象任務
  10. CallableDemo calTask= new CallableDemo();
  11. //送出任務并擷取執行結果
  12. Future<Integer> future =es.submit(calTask);
  13. //關閉線程池
  14. es.shutdown();
  15. try {
  16. Thread.sleep( );
  17. System.out.println( "主線程在執行其他任務");
  18. if(future.get()!= null){
  19. //輸出擷取到的結果
  20. System.out.println( "future.get()-->"+future.get());
  21. } else{
  22. //輸出擷取到的結果
  23. System.out.println( "future.get()未擷取到結果");
  24. }
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println( "主線程在執行完成");
  29. }
  30. }

執行結果:

Callable子線程開始計算啦!

主線程在執行其他任務 Callable子線程計算結束! future.get()-->12497500 主線程在執行完成

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

  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. public class CallableTest {
  7. public static void main(String[] args) {
  8. // //建立線程池
  9. // ExecutorService es = Executors.newSingleThreadExecutor();
  10. // //建立Callable對象任務
  11. // CallableDemo calTask=new CallableDemo();
  12. // //送出任務并擷取執行結果
  13. // Future<Integer> future =es.submit(calTask);
  14. // //關閉線程池
  15. // es.shutdown();
  16. //建立線程池
  17. ExecutorService es = Executors.newSingleThreadExecutor();
  18. //建立Callable對象任務
  19. CallableDemo calTask= new CallableDemo();
  20. //建立FutureTask
  21. FutureTask<Integer> futureTask= new FutureTask<>(calTask);
  22. //執行任務
  23. es.submit(futureTask);
  24. //關閉線程池
  25. es.shutdown();
  26. try {
  27. Thread.sleep( );
  28. System.out.println( "主線程在執行其他任務");
  29. if(futureTask.get()!= null){
  30. //輸出擷取到的結果
  31. System.out.println( "futureTask.get()-->"+futureTask.get());
  32. } else{
  33. //輸出擷取到的結果
  34. System.out.println( "futureTask.get()未擷取到結果");
  35. }
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. }
  39. System.out.println( "主線程在執行完成");
  40. }
  41. }

執行結果:

Callable子線程開始計算啦! 主線程在執行其他任務 Callable子線程計算結束! futureTask.get()-->12497500 主線程在執行完成

主要參考資料: java并發程式設計的藝術 相關文章:http://www.cnblogs.com/dolphin0520/p/3949310.html

java多線程-概念&建立啟動&中斷&守護線程&優先級&線程狀态( 多線程程式設計之一)

java多線程同步以及線程間通信詳解&消費者生産者模式&死鎖&Thread.join()( 多線程程式設計之二)

java&android線程池-Executor架構之ThreadPoolExcutor&ScheduledThreadPoolExecutor淺析(多線程程式設計之三)

Java多線程:Callable、Future和FutureTask淺析(多線程程式設計之四)

通過前面幾篇的學習,我們知道建立線程的方式有兩種,一種是實作Runnable接口,另一種是繼承Thread,但是這兩種方式都有個缺點,那就是在任務執行完成之後無法擷取傳回結果,那如果我們想要擷取傳回結果該如何實作呢?還記上一篇Executor架構結構中提到的Callable接口和Future接口嗎?,是的,從JAVA SE 5.0開始引入了Callable和Future,通過它們建構的線程,在任務執行完成後就可以擷取執行結果,今天我們就來聊聊線程建立的第三種方式,那就是實作Callable接口。

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

1.Callable<V>接口 我們先回顧一下java.lang.Runnable接口,就聲明了run(),其傳回值為void,當然就無法擷取結果了。

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

而Callable的接口定義如下

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

該接口聲明了一個名稱為call()的方法,同時這個方法可以有傳回值V,也可以抛出異常。嗯,對該接口我們先了解這麼多就行,下面我們來說明如何使用, 前篇文章我們說過,無論是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. public static <T> Callable<T> callable (Runnable task, T result)

2.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 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的實作

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接口,是以FutureTask也可以直接送出給Executor執行。 當然也可以調用線程直接執行(FutureTask.run())。接下來我們根據FutureTask.run()的執行時機來分析其所處的3種狀态: (1)未啟動,FutureTask.run()方法還沒有被執行之前,FutureTask處于未啟動狀态,當建立一個FutureTask,而且沒有執行FutureTask.run()方法前,這個FutureTask也處于未啟動狀态。 (2)已啟動,FutureTask.run()被執行的過程中,FutureTask處于已啟動狀态。 (3)已完成,FutureTask.run()方法執行完正常結束,或者被取消或者抛出異常而結束,FutureTask都處于完成狀态。

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

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

Java多線程程式設計:Callable、Future和FutureTask淺析(多線程程式設計之四)

分析: (1)當FutureTask處于未啟動或已啟動狀态時,如果此時我們執行FutureTask.get()方法将導緻調用線程阻塞;當FutureTask處于已完成狀态時,執行FutureTask.get()方法将導緻調用線程立即傳回結果或者抛出異常。 (2)當FutureTask處于未啟動狀态時,執行FutureTask.cancel()方法将導緻此任務永遠不會執行。 當FutureTask處于已啟動狀态時,執行cancel(true)方法将以中斷執行此任務線程的方式來試圖停止任務,如果任務取消成功,cancel(...)傳回true;但如果執行cancel(false)方法将不會對正在執行的任務線程産生影響(讓線程正常執行到完成),此時cancel(...)傳回false。 當任務已經完成,執行cancel(...)方法将傳回false。 最後我們給出FutureTask的兩種構造函數:

  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實作類如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.Callable;
  3. public class CallableDemo implements Callable<Integer> {
  4. private int sum;
  5. @Override
  6. public Integer call() throws Exception {
  7. System.out.println( "Callable子線程開始計算啦!");
  8. Thread.sleep( );
  9. for( int i= ;i< ;i++){
  10. sum=sum+i;
  11. }
  12. System.out.println( "Callable子線程計算結束!");
  13. return sum;
  14. }
  15. }

Callable執行測試類如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. public class CallableTest {
  6. public static void main(String[] args) {
  7. //建立線程池
  8. ExecutorService es = Executors.newSingleThreadExecutor();
  9. //建立Callable對象任務
  10. CallableDemo calTask= new CallableDemo();
  11. //送出任務并擷取執行結果
  12. Future<Integer> future =es.submit(calTask);
  13. //關閉線程池
  14. es.shutdown();
  15. try {
  16. Thread.sleep( );
  17. System.out.println( "主線程在執行其他任務");
  18. if(future.get()!= null){
  19. //輸出擷取到的結果
  20. System.out.println( "future.get()-->"+future.get());
  21. } else{
  22. //輸出擷取到的結果
  23. System.out.println( "future.get()未擷取到結果");
  24. }
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println( "主線程在執行完成");
  29. }
  30. }

執行結果:

Callable子線程開始計算啦!

主線程在執行其他任務 Callable子線程計算結束! future.get()-->12497500 主線程在執行完成

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

  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. public class CallableTest {
  7. public static void main(String[] args) {
  8. // //建立線程池
  9. // ExecutorService es = Executors.newSingleThreadExecutor();
  10. // //建立Callable對象任務
  11. // CallableDemo calTask=new CallableDemo();
  12. // //送出任務并擷取執行結果
  13. // Future<Integer> future =es.submit(calTask);
  14. // //關閉線程池
  15. // es.shutdown();
  16. //建立線程池
  17. ExecutorService es = Executors.newSingleThreadExecutor();
  18. //建立Callable對象任務
  19. CallableDemo calTask= new CallableDemo();
  20. //建立FutureTask
  21. FutureTask<Integer> futureTask= new FutureTask<>(calTask);
  22. //執行任務
  23. es.submit(futureTask);
  24. //關閉線程池
  25. es.shutdown();
  26. try {
  27. Thread.sleep( );
  28. System.out.println( "主線程在執行其他任務");
  29. if(futureTask.get()!= null){
  30. //輸出擷取到的結果
  31. System.out.println( "futureTask.get()-->"+futureTask.get());
  32. } else{
  33. //輸出擷取到的結果
  34. System.out.println( "futureTask.get()未擷取到結果");
  35. }
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. }
  39. System.out.println( "主線程在執行完成");
  40. }
  41. }

執行結果:

Callable子線程開始計算啦! 主線程在執行其他任務 Callable子線程計算結束! futureTask.get()-->12497500 主線程在執行完成

主要參考資料: java并發程式設計的藝術 相關文章:http://www.cnblogs.com/dolphin0520/p/3949310.html