Java 自學筆記 08 - ExecutorService & Future

Java 自學筆記 07 - Concurrent Queue 中我們使用 concurrent queue 來實作 Producer Consumer Problem,將生產者產出的 task 丟給 queue,消費者一有空閒就會去 queue 中拿新的 task 出來,實際上這就是 Thread Pool 的概念,不過過程中我們是自己去創建與管理 thread 和 queue,而 Java 其實提供了許多跟 Thread Pool 有關的介面,讓我們能夠更簡單的實現這些功能。

ExecutorService

Executor Service

ExecutorService 是 Java 中用於管理 Thread Pool 的 Interface,我們可以使用 Executors 類別下的 method 來去實作一個 ExecutorService,常見的方法有:

  • newSingleThreadExecutor(): 創建一個單個工作線程的線程池。
  • newFixedThreadPool(int n): 創建一個 n 個線程的線程池。
  • newCachedThreadPool(): 創建一個可根據需要自動擴展的線程池。

常用操作:

1
2
3
4
5
6
7
8
9
10
11
12
// new a ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(3);

// submit runnable tasks to ExecutorService
executor.submit(new RunnableTask());

// submit callable tasks to ExecutorService
executor.submit(new CallableTask());

// shutdown ExecutorService
executor.shutdown(); // smooth shutdown
executor.shutdownNow(); // force shutdown

從上面的程式碼中可以發現, ExecutorService 除了跟 Thread 一樣可以執行 Runnable 外,還多了一個 Callable,兩者非常相似,差別只在於 Callable 會回傳東西而 Runnable 不會,實作 Callable 的方法如下:

1
2
3
4
5
6
7
class CallableTask implements Callable<Integer> {
@Override
public Integer call(){
// ...
return 1;
}
}

call() 屬於 abstract method,一定要 override 掉,另外就是要注意泛型的資料型態。

Future

submit() 方法會返回一個 Future 物件,簡單來說 Future 是一個未來會得到的結果,以 submit 一個 Callable 來的話,其 Futrue 就是未來的某一時間點我們會得到 Callable 的返回值。越看覺得越熟悉,這就是 javascript 裡的 promise 跟非同步的概念呀 !
我們可以使用 get() 方法來取得結果,當呼叫 get() 時,主進程會被阻擋,直到 get() 取得返回值才會繼續,舉例來說:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.*;

class CallableTask implements Callable<String> {
@Override
public String call() throws InterruptedException{
System.out.println("Task started.");
Thread.sleep(3000);
return "Task finished.";
}
}

public class ThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException{
ExecutorService executor = Executors.newFixedThreadPool(3);

Future<String> future = executor.submit(new CallableTask());
System.out.println("Main thread keeps running.");

// main thread is blocked until task return
String str = future.get();
System.out.println(str);;

System.out.println("Main thread continues.");

executor.shutdown();
}
};

執行結果如下:

1
2
3
4
Main thread keeps running.
Task started.
Task finished.
Main thread continues.

參考資料

Java多執行緒的基本知識
Difference between Executor, ExecutorService and Executers class in Java