Java中的Thread Pool

潘力豪 2021/11/09 10:46:41
2746

ThreadPool

在Java中想要做非同步程式開發就是建立Thread去執行,但是建立Thread是有成本的,因此如果能將Thread重複利用,則可節省效能,較好的方式就是利用Thread Pool,本文將介紹如何在Java中建立及使用Thread Pool。

ThreadPoolExecutor

在Java1.5後的版本提出的Executor去管控Thread Pool,而ThreadPoolExecutor則可以客製化不同模式的Thread Pool,以下為一建立ThreadPoolExecutor範例

 

ExecutorService executorService = new ThreadPoolExecutor( //
				int corePoolSize, //
				int maxPoolSize, //
				long keepAliveSeconds, //
				TimeUnit unit, //
				BlockingQueue<Runnable> workQueue, //
				ThreadFactory threadFactory, //
				RejectedExecutionHandler handler //
		);

- corePoolSize:

   Thread的數量,建立的Thread數量不會少於此設定。

- maxPoolSize:

    Thread Pool的最大數量,如果所有的Thread都被執行的話,Task會被塞到Queue中,等到有空閒的Thread去執行Task,此數值最好根據系統資源定義出來。

- keepAliveTime:

    當閒置時間超過此設定值,系統會開始回收corePoolSize以上多餘的Thread。

- unit:

    keepAliveTime的時間單位。

- workQueue:

    當所有的Thread都被執行時,Task要用何種方式在Queue中等待。

- threadFactory:

    用以創建新的Thread方式,即可依需求客製化Thread,不需要手動在用new Thread()。

- handler:

    Queue已滿且Thread已達到maxPoolSize之後會用什麼方式處理新的Task。

BlockingQueue

如上所述,當所有Thread都被執行時,Task會被放入Queue中等待,而其執行方式有一定的規則

  1. 當前Thread數量小於corePoolSize設定時,則會新增Thread,直到Thread數量大於corePoolSize設定值。
  2. 當Thread數量大於corePoolSize時,則會將Task放入Queue中等待。
  3. 當Task無法再被放入Queue中時,則會建立新的Thread至超過maxPoolSize為止。
  4. 當Thread數量超過maxPoolSize時,則Task會被拒絕。

BlockingQueue有以下三種類型

SynchronousQueue

Queue的Size為0,會直接把Task交給Thread,如超過corePoolSize時,則看是否有設定maxPoolSize,如超過maxPoolSize是有可能拒絕Task,因此使用此類型Queue時,建議不要設定maxPoolSize。

LinkedBlockingQueue

Queue的Size無限制,因為是無限制,所以當Task執行時間過長時有可能造成大量Task卡在Queue中,而導致OOM的發生。

ArrayBlockingQueue

Queue的Size是有限制的,Queue的Size必須要跟Thread Pool相互搭配才行,數量大的Queue Size和數量小的Thread Pool Size可有效降低CPU使用率,但會降低QPS,反之,則提高CPU使用率跟提高QPS。

RejectExecutionHandle

當Queue飽和時,可以根據handle做出相對應的處理方式,共有四種處理的方式。

AbortPolicy

使用此種策略是當Queue數量飽和時,會拋出RejectedExecutionException

DiscardPolicy

使用此種策略是當Queue數量飽和時,不做處理直接拋棄。

DiscardOldestPolicy

使用此種策略是當Queue數量飽和時,將Queue的第一個Task拋棄,並嘗試重新提交Task

CallerRunsPolicy

使用此種策略是當Queue數量飽和時,直接呼叫原本的主要Thread來執行Task,但這期間主要的Thread則無法提交新的Task,直到Thread Pool有時間把處理中的Task給完成。

總結

透過ThreadPoolExcutor建立出Thread Pool後則可使用以下方式呼叫,開始非同步的工作執行。

executorService.execute(new Runnable() {
    public void run() {
        System.out.println("start task");
    }
})

當整個應用程式可能已經到達終止的時間點時,有可能因為Thread Pool還持續在執行工作而導致JVM無法被停止,因次最好是在程式中主動關閉Thread Pool,可以呼叫由ExecutorSerivce提供的shutdown()方法達到主動停止的效果。

潘力豪