ExecutorService And Thread Pool

James Lin
7 min readMay 2, 2019

--

本篇會介紹
1. 為什麼要用 Thread Pool
2. 介紹 Executor
3. 介紹 ExecutorService 的四種物件(newFixedThreadPool… )
4. 使用 ExecutorService
5. 使用 ThreadPoolExecutor

為什麼要用 Thread Pool ?

我想要這樣使用 Thread 不行嗎 ?

new Thread(new Runnable() {
@Override
public void run() {
// do something
}
}).start();

如果這樣的 code 在 Project 到處跑會發生什麼事呢 ?

無限制地開啟 Thread,code 很髒

這時資源就會瘋狂地被浪費掉,很有可能會發生 Out Of Memory,增加設備跟設定環境條件就好啦。

增加設備跟設定環境條件就好啦。這是好做法嗎 ? 聽起來不太像

這就是要使用 Thread Pool 的原因

介紹 Executor

Java 的文件中有提到 Executor 是將任務(Task )與任務的執行者(Thread)分離。

那麼此 Interface 又有兩個 sub Interface
ExecutorService:用於終止或 create 追蹤非同步行為的 Future 物件。
ScheduledExecutorService:可以讓任務定期執行(ex:每過10 秒執行一次 ),或者延遲執行(ex:10 秒後執行)。

ExecutorService 的四種物件

newFixThreadPool:Create 一個 Thread Pool,必須指定 Thread Pool 最多能有幾條 Thread,每當 submit 一個任務(Task)就會 Create 一條 Thread 直到最大的限度。(如果 Thread 因為 Exception 而結束,Thread Pool 會補充一個新的 Thread)

newCachedThreadPool:Create 一個 Thread Pool,這個 Pool 並無限制最多能容納幾條 Thread,會依據需求去 Create Thread 與 回收 Thread。

newSingleThreadExecutor:只會有一條 Thread,任務(Task)會依照一定的順序執行(ex:FIFO, LIFO)。(如果 Thread 因為 Exception 而結束,會有一個新的 Thread 取代原本的 Thread)

newScheduledThreadPool:Create 一個 Thread Pool,必須指定 Thread Pool 最多能有幾條 Thread,支援周期性的任務(ex:每過10 秒執行一次 )或者延遲性任務(ex:10 秒後執行)。

在 Java Concurrency in Practice 一書中有提到:

選擇策略的依據

任務在什麼(what) Thread 執行

任務以什麼(what) 順序執行

可以有多少個(how many)任務 concurrency 執行

如果系統 overload 需要放棄一個任務,應該挑選哪一個(which)?如何(how)通知應用程式知道這件事

任務執行前與結束後,應該要做什麼(what)?

使用 ExecutorService

這邊只是示範如何使用 newFixThreadPool,其餘的請查詢 Java doc。

首先 Create 一個 newFixThreadPool 並指定 Thread Pool 最多可容納 3 條 Thread

ExecutorService executorServiceNewFixedThreadPool =   
Executors.newFixedThreadPool(3);

接著 Create 一個任務(Task),這個任務會 print 出 Thread 的資訊

Runnable run = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};

接著 submit 此任務(Task) 4 次(超出容納的數目)

executorServiceNewFixedThreadPool.submit(run);executorServiceNewFixedThreadPool.submit(run);executorServiceNewFixedThreadPool.submit(run);executorServiceNewFixedThreadPool.submit(run);

結果,發現 Thread-3 被派了兩份 Task,沒有超出限制

pool-1-thread-2pool-1-thread-3pool-1-thread-3pool-1-thread-1

使用 ThreadPoolExecutor

此 class 是 Java 提供的 API,比上述幾種模式更靈活的管理 Thread,有些狀況需要將 newFixThreadPool 與 newCachedThreadPool 並用,此時可以使用 ThreadPoolExecutor,如果有更複雜的狀況則可以自定義 ThreadFactory 制定策略。

使用最簡單的 ThreadPoolExecutor

CorePoolSize:會在 Thread Pool 一直存活 Thread 的數量,除非有設定 allowCoreThreadTimeOut 不然 Thread Pool 裡面最低會有 CorePoolSize 條 Thread 活著。

maximumPoolSize:Thread Pool 裡面最多會有幾條 Thread,一定要大於 CorePoolSize。

keepAliveTime:當 Thread Pool 裡面 Thread 的數量比 CorePoolSize 還多時,過幾個時間單位會被回收。

unit:定義時間單位(ex:時 分 秒 天 日)

workQueue:任務排隊的容器,可以在這邊定義 FIFO LIFO。

這裡有幾條規則:

1. 如果 Thread Pool 裡面 Thread 的數量比 CorePoolSize 小,Executor 會傾向 
Create 一個 Thread 去執行任務。
2. 如果Thread Pool 裡面 Thread 的數量比 CorePoolSize 大,Executor 會傾向
將"任務"加入 workQueue。
3. 如果任務無法被排隊,Executor 則會 Create 一個 Thread 去執行任務,此時
Thread Pool 裡面 Thread 的數量比 maximumPoolSize 大,則會拒絕此任務。

CorePoolSize 的數量可以由底層硬體來定義

int corePoolSize = Runtime.getRuntime().availableProcessors();

使用起來大概像是這樣

BlockingQueue<Runnable> bQ = new LinkedBlockingQueue<Runnable>();
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 3,
10,
TimeUnit.SECONDS,
bQ);

threadPoolExecutor.submit(run);

--

--