“池”技術對我們來說是非常熟悉的一個概念,它的引入是為了在某些場景下提高系統某些關鍵節點性能,最典型的例子就是資料庫連接配接池,JDBC是一種服務供應接口(SPI),具體的資料庫連接配接實作類由不同廠商實作,資料庫連接配接的建立和銷毀都是很耗時耗資源的操作,為了查詢資料庫中某條記錄,最原始的一個過程是建立連接配接、發送查詢語句、傳回查詢結果、銷毀連接配接,假如僅僅是一個很簡單的查詢語句,那麼可能建立連接配接與銷毀連接配接兩個步驟就已經占所有資源時間消耗的絕大部分,如此低下的效率顯然讓人無法接受。針對這個過程是否能通過某些手段提高效率,于是想到的盡可能減少建立和銷毀連接配接操作,因為連接配接相對于查詢是無狀态的,不必每次查詢都重新生成銷毀,我們可以把這些通道維護起來供下一次查詢使用,維護這些管道的工作就交給了“池”。
線程池也是類似于資料庫連接配接池的一種池,而僅僅是把池裡的對象換成了線程。線程是為多任務而引入的概念,每個線程在任意時刻執行一個任務,假如多個任務要并發執行則要用到多線程技術。每個線程都有自己的生命周期,以建立為始銷毀為末。如下圖,兩個線程運作階段占整個生命周期的比重不同,運作階段所占比重小的線程可以認為其運作效率低,反觀下面一條線程則認為運作效率高。在大多數場景下都比較符合圖上面的線程運作模式,例如我們常見的web服務、資料庫服務等等。為了提高運作效率引入線程池,它的核心思想就是把運作階段盡量拉長,對于每個任務的到來不是重複建立銷毀線程,而是重複利用之前建立好的線程執行任務。

其中一種方案是在系統啟動事建立好一定數量的線程并做好線程維護工作,一旦有任務到來即從線程池中取出一條空閑的線程執行任務。原理聽起來比較清晰,但現實中對于一條線程,一旦調用start方法後就将運作任務直到任務完成,随後JVM将對線程對象進行GC回收,如此一來線程不就銷毀了嗎?是的,是以需要換種思維角度,讓這些線程啟動後通過一個無限循環來執行指定的任務,下面将重點講解如何實作線程池。
一個線程池的屬性起碼包含初始化線程數量、線程數組、任務隊列。初始化線程數量指線程池初始化的線程數,線程數組儲存了線程池中所有線程,任務隊列指添加到線程池等待處理的所有任務。如下圖,線程池裡有兩條線程,池裡線程的工作就是不斷循環檢測任務隊列中是否有需要執行的任務,如果有則處理并移出任務隊列。于是可以說線程池中的所有線程的任務就是不斷檢測任務隊列并不斷執行隊列中的任務。
看一個最簡單粗糙的線程池的實作,使用線程池是隻需執行個體化一個對象,構造函數會建立相應數量的線程并啟動線程,啟動的線程無限循環檢測任務隊列,執行方法execute()僅僅把任務添加到任務隊列中。有一點需要注意的是所有任務都必須實作Runnable接口,這是線程池的任務隊列與工作線程的約定,juc工具包作者Doug Lea大神當時如此規定,工作線程檢測任務隊列并調用隊列的run()方法,假如你自己重新寫一個線程池是完全可以自己定義一個不一樣的任務接口。一個完善的線程池并不像下面例子簡單,需要提供啟動、銷毀、增加工作線程的政策、最大工作線程數、各種狀态的擷取等等操作,而且工作線程也不可能老是做無用循環,需要對任務隊列使用wait、notify優化或任務隊列改用阻塞隊列。
public final class ThreadPool {
private final int worker_num;
private WorkerThread[] workerThrads;
private List<Runnable> taskQueue = new LinkedList<Runnable>();
private static ThreadPool threadPool;
public ThreadPool(int worker_num) {
this.worker_num = worker_num;
workerThrads = new WorkerThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workerThrads[i] = new WorkerThread();
workerThrads[i].start();
}
public void execute(Runnable task) {
synchronized (taskQueue) {
taskQueue.add(task);
private class WorkerThread extends Thread {
public void run() {
Runnable r = null;
while (true) {
if (!taskQueue.isEmpty()) {
r = taskQueue.remove(0);
r.run();
通過上面已經清楚了線程池原理,但并不提倡重造輪子行為,因為線程池處理不好很容易産生死鎖問題,同時線程池内狀态同步操作不當也可能導緻意想不到的問題,除此之外還有很多其他的并發問題,除非是很有經驗的并發程式員才能盡可能減少可能的錯誤。我們直接使用jdk的juc工具包即可,它由Doug Lea編寫的優秀并發程式工具,單線程池就已經提供了好多種類的線程池,實際開發中根據需求選擇合适的線程池。
<a target="_blank" href="https://item.jd.com/12185360.html">點選訂購作者書籍《Tomcat核心設計剖析》</a>