執行緒執行 之 Runnable Callable Future ,FutureTask ExcutorService概覽

xiaoliuliu2050發表於2018-07-17

Runnable 和Callable介面的區別

執行緒的建立方式中有兩種,一種是實現Runnable介面,另一種是繼承Thread,但是這兩種方式都有個缺點,那就是在任務執行完成之後無法獲取返回結果,於是就有了Callable介面,Future介面與FutureTask類的配合取得返回的結果。

我們先回顧一下java.lang.Runnable介面,就宣告瞭run(),其返回值為void,當然就無法獲取結果。

public interface Runnable { public abstract void run(); }

而Callable的介面定義如下
public interface Callable<V> { V call() throws Exception; }

該介面宣告瞭一個名稱為call()的方法,同時這個方法可以有返回值V,也可以丟擲異常。嗯,對該介面我們先了解這麼多就行,下面我們來說明如何使用,

ExecutorService 介面的用途:(執行緒池的高階抽象介面)

無論是Runnable介面的實現類還是Callable介面的實現類,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都實現了ExcutorService介面,而因此Callable需要和Executor框架中的ExcutorService結合使用,我們先看看ExecutorService提供的方法:
<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);

第一個方法:submit提交一個實現Callable介面的任務,並且返回封裝了非同步計算結果的Future。

第二個方法:submit提交一個實現Runnable介面的任務,並且指定了在呼叫Future的get方法時返回的result物件。(不常用)

第三個方法:submit提交一個實現Runnable介面的任務,並且返回封裝了非同步計算結果的Future。
因此我們只要建立好我們的執行緒物件(實現Callable介面或者Runnable介面),然後通過上面3個方法提交給執行緒池去執行即可。

這三個方法的內部實現:

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

其中newTaskFor 的實現

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

可以看出不管submit 的是什麼內容,都會被封裝成 FutureTask。

execue(ftask) 的邏輯請參考 https://blog.csdn.net/xiaoliuliu2050/article/details/87882438

還有點要注意的是,除了我們自己實現Callable物件外,我們還可以使用工廠類Executors來把一個Runnable物件包裝成Callable物件。Executors工廠類提供的方法如下:

public static Callable<Object> callable(Runnable task)

public static <T> Callable<T> callable(Runnable task, T result)

 

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登場啦。

 

FutureTask類

我們先來看看FutureTask的實現,FutureTask類實現了RunnableFuture介面,

    public class FutureTask<V> implements RunnableFuture<V> ;

我們看一下RunnableFuture介面的實現:

public interface RunnableFuture<V> extends Runnable, Future<V> {

   void run();

}

分析:FutureTask除了實現了Future介面外還實現了Runnable介面(即可以通過Runnable介面實現執行緒,也可以通過Future取得執行緒執行完後的結果),因此FutureTask也可以直接提交給Executor執行。
最後我們給出FutureTask的兩種建構函式:

public FutureTask(Callable<V> callable) {}

public FutureTask(Runnable runnable, V result) {}

 

Callable<V>/Future<V>/FutureTask的具體使用場景(封裝了非同步獲取結果的Future!!!)

假如有這樣的場景,我們現在需要計算一個資料,而這個資料的計算比較耗時,而我們後面的程式也要用到這個資料結果,那麼這個時Callable豈不是最好的選擇?我們可以開設一個執行緒去執行計算,而主執行緒繼續做其他事,而後面需要使用到這個資料時,我們再使用Future獲取不就可以了嗎?下面我們就來編寫一個這樣的例項

方式1 使用Callable+Future獲取執行結果

Callable實現類如下:

public class CallableDemo implements Callable<Integer> {

  1. private int sum;

  2. @Override

  3. public Integer call() throws Exception {

  4. System.out.println("Callable子執行緒開始計算啦!");

  5. Thread.sleep(2000);

  6. for(int i=0 ;i<5000;i++){

  7. sum=sum+i;

  8. }

  9. System.out.println("Callable子執行緒計算結束!");

  10. return sum;

  11. }

  12. }

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. } catch (Exception e) {

  23. e.printStackTrace();

  24. }

  25. System.out.println("主執行緒在執行完成");

  26. }

  27. }

執行結果:

  1. Callable子執行緒開始計算啦!

  2. 主執行緒在執行其他任務

  3. Callable子執行緒計算結束!

  4. future.get()-->12497500

  5. 主執行緒在執行完成

方式 2 使用Callable+FutureTask獲取執行結果

  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. } catch (Exception e) {

  25. e.printStackTrace();

  26. }

  27. System.out.println("主執行緒在執行完成");

  28. }

  29. }

執行結果:

  1. Callable子執行緒開始計算啦!

  2. 主執行緒在執行其他任務

  3. Callable子執行緒計算結束!

  4. futureTask.get()-->12497500

  5. 主執行緒在執行完成

相關文章