1. 程式人生 > >Java執行緒池ExecutorService 的理解與使用

Java執行緒池ExecutorService 的理解與使用

介面 java.util.concurrent.ExecutorService 表述了非同步執行的機制,並且可以讓任務在後臺執行。一個 ExecutorService 例項因此特別像一個執行緒池。事實上,在 java.util.concurrent 包中的 ExecutorService 的實現就是一個執行緒池的實現。

ExecutorService 樣例

這裡有一個簡單的使用Java 實現的 ExectorService 樣例:

1   ExecutorService executorService = Executors.newFixedThreadPool(10);
2    
3
executorService.execute(new Runnable() { 4 public void run() { 5 System.out.println("Asynchronous task"); 6 } 7 }); 8 9 executorService.shutdown();

首先使用 newFixedThreadPool() 工廠方法建立一個 ExecutorService ,上述程式碼建立了一個可以容納10個執行緒任務的執行緒池。其次,向 execute() 方法中傳遞一個非同步的 Runnable 介面的實現,這樣做會讓 ExecutorService 中的某個執行緒執行這個 Runnable 執行緒。

任務的委託(Task Delegation)

下方展示了一個執行緒的把任務委託非同步執行的ExecutorService的示意圖。
示意圖
壹旦執行緒把任務委託給 ExecutorService,該執行緒就會繼續執行與執行任務無關的其它任務。

ExecutorService 的實現

由於 ExecutorService 只是一個介面,你壹量需要使用它,那麼就需要提供一個該介面的實現。ExecutorService 介面在 java.util.concurrent 包中有如下實現類:

ThreadPoolExecutor

ScheduledThreadPoolExecutor

建立一個 ExecutorService

你可以根據自己的需要來建立一個 ExecutorService ,也可以使用 Executors 工廠方法來建立一個 ExecutorService 例項。這裡有幾個建立 ExecutorService 的例子:

1   ExecutorService executorService1 = Executors.newSingleThreadExecutor();
2   ExecutorService executorService2 = Executors.newFixedThreadPool(10);
3   ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

ExecutorService 使用方法

這裡有幾種不同的方式讓你將任務委託給一個 ExecutorService:

1   execute(Runnable)
2   submit(Runnable)
3   submit(Callable)
4   invokeAny(...)
5   invokeAll(...)

我會在接下來的內容裡把每個方法都看壹遍。

execute(Runnable)

方法 execute(Runnable) 接收一個 java.lang.Runnable 物件作為引數,並且以非同步的方式執行它。如下是一個使用 ExecutorService 執行 Runnable 的例子:

1   ExecutorService executorService = Executors.newSingleThreadExecutor();
2    
3   executorService.execute(new Runnable() {
4       public void run() {
5           System.out.println("Asynchronous task");
6       }
7   });
8        
9   executorService.shutdown();

使用這種方式沒有辦法獲取執行 Runnable 之後的結果,如果你希望獲取執行之後的返回值,就必須使用 接收 Callable 引數的 execute() 方法,後者將會在下文中提到。

submit(Runnable)

方法 submit(Runnable) 同樣接收一個 Runnable 的實現作為引數,但是會返回一個 Future 物件。這個 Future 物件可以用於判斷 Runnable 是否結束執行。如下是一個 ExecutorService 的 submit() 方法的例子:

1   Future future = executorService.submit(new Runnable() {
2       public void run() {
3           System.out.println("Asynchronous task");
4       }
5   });
6   //如果任務結束執行則返回 null
7   System.out.println("future.get()=" + future.get());
submit(Callable)

方法 submit(Callable) 和方法 submit(Runnable) 比較類似,但是區別則在於它們接收不同的引數型別。Callable 的例項與 Runnable 的例項很類似,但是 Callable 的 call() 方法可以返回一個結果。方法 Runnable.run() 則不能返回結果。

Callable 的返回值可以從方法 submit(Callable) 返回的 Future 物件中獲取。如下是一個 ExecutorService Callable 的樣例:

1   Future future = executorService.submit(new Callable(){
2       public Object call() throws Exception {
3           System.out.println("Asynchronous Callable");
4           return "Callable Result";
5       }
6   });
7    
8   System.out.println("future.get() = " + future.get());

上述樣例程式碼會輸出如下結果:

1   Asynchronous Callable
2   future.get() = Callable Result

inVokeAny()

方法 invokeAny() 接收一個包含 Callable 物件的集合作為引數。呼叫該方法不會返回 Future 物件,而是返回集合中某一個 Callable 物件的結果,而且無法保證呼叫之後返回的結果是哪一個 Callable,只知道它是這些 Callable 中一個執行結束的 Callable 物件。
如果一個任務執行完畢或者丟擲異常,方法會取消其它的 Callable 的執行。
以下是一個樣例:

01  ExecutorService executorService = Executors.newSingleThreadExecutor();
02   
03  Set<Callable<String>> callables = new HashSet<Callable<String>>();
04   
05  callables.add(new Callable<String>() {
06      public String call() throws Exception {
07          return "Task 1";
08      }
09  });
10  callables.add(new Callable<String>() {
11      public String call() throws Exception {
12          return "Task 2";
13      }
14  });
15  callables.add(new Callable<String>() {
16      public String call() throws Exception {
17          return "Task 3";
18      }
19  });
20   
21  String result = executorService.invokeAny(callables);
22   
23  System.out.println("result = " + result);
24   
25  executorService.shutdown();

以上樣例程式碼會打印出在給定的集合中的某一個 Callable 的返回結果。我嘗試運行了幾次,結果都在改變。有時候返回結果是”Task 1”,有時候是”Task 2”,等等。

invokeAll()

方法 invokeAll() 會呼叫存在於引數集合中的所有 Callable 物件,並且返回一個包含 Future 物件的集合,你可以通過這個返回的集合來管理每個 Callable 的執行結果。
需要注意的是,任務有可能因為異常而導致執行結束,所以它可能並不是真的成功運行了。但是我們沒有辦法通過 Future 物件來了解到這個差異。
以下是一個程式碼樣例:

01  ExecutorService executorService = Executors.newSingleThreadExecutor();
02   
03  Set<Callable<String>> callables = new HashSet<Callable<String>>();
04   
05  callables.add(new Callable<String>() {
06      public String call() throws Exception {
07          return "Task 1";
08      }
09  });
10  callables.add(new Callable<String>() {
11      public String call() throws Exception {
12          return "Task 2";
13      }
14  });
15  callables.add(new Callable<String>() {
16      public String call() throws Exception {
17          return "Task 3";
18      }
19  });
20   
21  List<Future<String>> futures = executorService.invokeAll(callables);
22   
23  for(Future<String> future : futures){
24      System.out.println("future.get = " + future.get());
25  }
26   
27  executorService.shutdown();

ExecuteService 服務的關閉

當使用 ExecutorService 完畢之後,我們應該關閉它,這樣才能保證執行緒不會繼續保持執行狀態。
舉例來說,如果你的程式通過 main() 方法啟動,並且主執行緒退出了你的程式,如果你還有一個活動的 ExecutorService 存在於你的程式中,那麼程式將會繼續保持執行狀態。存在於 ExecutorService 中的活動執行緒會阻止Java虛擬機器關閉。
為了關閉在 ExecutorService 中的執行緒,你需要呼叫 shutdown() 方法。ExecutorService 並不會馬上關閉,而是不再接收新的任務,壹但所有的執行緒結束執行當前任務,ExecutorServie 才會真的關閉。所有在呼叫 shutdown() 方法之前提交到 ExecutorService 的任務都會執行。
如果你希望立即關閉 ExecutorService,你可以呼叫 shutdownNow() 方法。這個方法會嘗試馬上關閉所有正在執行的任務,並且跳過所有已經提交但是還沒有執行的任務。但是對於正在執行的任務,是否能夠成功關閉它是無法保證 的,有可能他們真的被關閉掉了,也有可能它會壹直執行到任務結束。這是一個最好的嘗試。