天天看點

ExecutorService簡介

作者:JAVA微學堂
ExecutorService簡介

概述

ExecutorService是一個JDK API,它簡化了異步模式下運作任務。一般來說,ExecutorService會自動提供一個線程池和一個用于為其配置設定任務的API。

執行個體化ExecutorService

  • 使用Executors

建立ExecutorService最簡單的方法是使用Executors類的一個工廠方法。

例如,建立一個包含10個線程的線程池:

ExecutorService executor = Executors.newFixedThreadPool(10);           
  • 直接建立ExecutorService

ThreadPoolExecutor類有一些構造函數,可以使用它們來配置ExecutorService及其内部池:

ExecutorService executorService = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,   
  new LinkedBlockingQueue<Runnable>());           

上面的代碼與工廠方法newSingleThreadExecutor()的源代碼非常相似。

将任務配置設定給ExecutiorService

ExecutorService可以執行Runnable和Callable的任務:

Runnable runnableTask = () -> {
    try {
        TimeUnit.MILLISECONDS.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Callable<String> callableTask = () -> {
    TimeUnit.MILLISECONDS.sleep(300);
    return "Task's execution";
};

List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);           

我們可以使用幾種方法将任務配置設定給ExecutorService,包括從Executor接口繼承的execute(),以及submit()、invokeAny()和invokeAll()。

execute()方法是傳回void,不提供任何擷取任務執行結果或檢查任務狀态(是否正在運作)的可能性:

executorService.execute(runnableTask);           

submit()将Callable或Runnable任務送出給ExecutorService,并傳回Future類型的結果:

Future<String> future = 
  executorService.submit(callableTask);           

invokeAny()将一組任務配置設定給ExecutorService,使每個任務運作,并傳回一個任務成功執行的結果(如果執行成功):

String result = executorService.invokeAny(callableTasks);           

invokeAll()将任務集合配置設定給ExecutorService,使每個任務運作,并以Future類型的對象清單的形式傳回所有任務執行的結果:

List<Future<String>> futures = executorService.invokeAll(callableTasks);           

關閉ExecutorService

一般來說,當沒有任務要處理時,ExecutorService不會被自動銷毀。為了正确關閉ExecutorService,有shutdown()和shutdownNow()API。

shutdown()方法不會立即銷毀ExecutorService,它将使ExecutorService停止接受新任務,并在所有正在運作的線程完成目前工作後關閉:

executorService.shutdown();           

shutdownNow()方法試圖立即銷毀ExecutorService,但它不能保證所有正在運作的線程都會同時停止:

List<Runnable> notExecutedTasks = executorService.shutDownNow();           

此方法傳回等待處理的任務清單,由開發人員決定如何處理這些任務。

關閉ExecutorService的一個好方法(Oracle也建議這樣做)是将這兩種方法與awaitTermination()方法結合使用:

executorService.shutdown();
try {
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
        executorService.shutdownNow();
    } 
} catch (InterruptedException e) {
    executorService.shutdownNow();
}           

使用這種方法,ExecutorService将首先停止執行新任務,然後等待一段指定的時間,等待所有任務完成。如果該時間到期,則立即停止執行。

Future接口

submit()和invokeAll()方法傳回一個對象或Future類型的對象集合,這使我們能夠獲得任務執行的結果或檢查任務的狀态(是否正在運作)。

Future接口提供了一個特殊的阻塞方法get(),它傳回Callable任務執行的實際結果,或者在Runnable任務的情況下傳回null:

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
    result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}           

在任務仍在運作時調用get()方法将導緻執行阻塞,直到任務正确執行并且結果可用。

由于get()方法導緻的阻塞時間很長,應用程式的性能可能會降低。如果生成的資料不重要,則可以通過使用逾時來避免此類問題:

String result = future.get(200, TimeUnit.MILLISECONDS);           

如果執行周期長于指定的時間(在本例中為200毫秒),則會引發TimeoutException。

我們可以使用isDone()方法來檢查配置設定的任務是否已經處理。

Future接口還提供了使用cancel()方法取消任務執行,并使用isCancelled()方法檢查取消:

boolean canceled = future.cancel(true);
boolean isCancelled = future.isCancelled();           

ScheduledExecutorService排程服務接口

ScheduledExecutiorService在一些預定義的延遲和/或周期性地運作任務。執行個體化ScheduledExecutorService的最佳方法是使用Executors類的工廠方法。

比如使用帶有一個線程的ScheduledExecutorService:

ScheduledExecutorService executorService = Executors
  .newSingleThreadScheduledExecutor();           

要在固定延遲後安排單個任務的執行,可使用ScheduledExecutiorService的scheduled()方法。

兩個scheduled()方法允許執行Runnable或Callable任務:

Future<String> resultFuture = 
  executorService.schedule(callableTask, 1, TimeUnit.SECONDS);           

上面的代碼在執行callableTask之前會延遲一秒鐘,scheduleAtFixedRate()方法允許我們在固定延遲後定期運作任務。

以下代碼塊将在初始延遲100毫秒後運作任務。之後,它将每450毫秒運作一次相同的任務:

executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);           

如果處理器需要比scheduleAtFixedRate()方法的period參數更多的時間來運作配置設定的任務,則ScheduledExecutiorService将等到目前任務完成後再啟動下一個任務。

如果任務的疊代之間需要固定長度的延遲,則應使用scheduleWithFixedDelay()。

例如,以下代碼将保證在目前執行結束和另一個執行開始之間有150毫秒的暫停:

executorService.scheduleWithFixedDelay(task, 100, 150, TimeUnit.MILLISECONDS);           

根據scheduleAtFixedRate()和scheduleWithFixedDelay()方法約定,任務的周期執行将在ExecutorService終止時結束,或者如果在任務執行過程中引發異常。

結論

ExecutorService的最佳用例是根據“一個線程一個任務”的方案處理獨立任務,例如事務或請求。

使用ExecutorService需要注意的問題:

  • 确定應用程式需要多少線程才能有效運作任務非常重要。太大的線程池将導緻不必要的開銷,隻為了建立大部分處于等待模式的線程。太少可能會使應用程式看起來沒有響應,因為隊列中的任務等待時間很長。
  • 在任務取消後調用Future的get()方法結果會觸發CancellationException。
  • Future的get()方法如果長時間阻塞,應該使用逾時來避免意外的等待。
  • 已不使用的ExecutorService可考慮關閉減少開銷。

繼續閱讀