概述
在上一篇文章 徹底搞定線程池(1)-線程池模型的構建 中,我把線程池的主要的功能和流程給梳理了一下,並且在最後使用代碼實現了一個簡單的線程池。不過距離一個完整的線程池還需要多做一些東西。
本篇的代碼會在上一版代碼的基礎上進行修改。地址爲 github地址,其中steap1
爲上一篇的代碼,steap2
爲本篇代碼
這裏先把線程池的功能羅列出來,之後一點點的把功能實現。
線程池需要提供的接口
- 任務的提交
- 溫和的關閉線程池:把已提交的任務完成,但拒絕新任務提交
- 強制關閉:不再執行任務,並且嘗試停止正在運行的任務
線程池內部需要實現的功能
- 線程的創建和管理
- 任務隊列的管理
- 處理任務(不斷從隊列中獲取任務來處理)
- 線程的閒置時間限制(可以設定閒置一定時間後停止該線程)
線程池中特殊情況的處理
- 飽和策略:有界的任務隊列中,如果任務放滿的了該如何處理
- 線程中斷重啓:在執行任務時線程被中斷了,需要及時的新開一個線程來繼續運行
流程圖
根據線程的功能需求,將任務提交的流程畫出來:
在線程池中,線程的運行過程也整理一下:
代碼實現
這裏把一些關鍵的代碼實現拿出來講講,反正都是按照整理好的流程來寫的。詳細的源碼在這裏,看懂我這個代碼,說明對線程池有足夠的瞭解,這時候去看Java的ThreadPoolExecutor
源碼或相關的設計,就不會有云裏霧裏的感覺。
線程池的狀態管理
線程池的狀態通過state
變量來控制,當結束時,改變state的值即可。其他線程在運行的時候時檢測狀態,做出處理。
//線程池的狀態
private volatile int state = STATE_RUNNING;
//線程池的2種狀態
private final static int STATE_RUNNING = -1;
private final static int STATE_SHUTDOWN = 0;
private final static int STATE_SHUTDOWN_NOW = 2;
//工作線程集合
private volatile Set<Worker> workers;
/**
* 停止線程池
* 溫和的停止,拒絕提交新任務,但是已提交的任務會全部完成
*/
public void shutdown() {
this.state = STATE_SHUTDOWN;
}
/**
* 強制停止線程池
* 拒絕提交任務,剩下的任務不再執行,並嘗試中斷線程池
*/
public void shutdownNow() {
this.state = STATE_SHUTDOWN_NOW;
for(Worker w:workers){
try {
w.t.interrupt();
}catch (Exception e){
e.printStackTrace();
}
}
}
任務的提交
/**
* 任務提交接口
*/
public void execute(Task task) {
if (state > STATE_RUNNING)
throw new RejectedExecutionException("線程池已關閉,禁止提交任務");
if (workers.size() < poolSize) {
addWorker(task);
} else {
this.queue.add(task);
}
}
/**
* 添加worker工作線程,並立即執行
* 這裏做個雙重判定,防止併發時多創建了線程
*/
private void addWorker(Task task) {
mainLock.lock();
try {
if (workers.size() >= poolSize) {
this.queue.add(task);
return;
}
Worker w = new Worker(task);
workers.add(w);
w.t.start();
} finally {
mainLock.unlock();
}
}
線程工作流程、線程異常中斷處理、線程閒置處理
/**
* 工作線程實際運行任務的方法
*/
void runWorker(Worker worker) {
Task task = (Task) worker.task;
boolean completedAbruptly = false;
try {
while (true) {
//線程在這個循環中不斷的獲取任務來執行
// getTask() 爲從隊列中獲取任務,如果爲null,表示該線程超過閒置時間限制,停止該線程
if (task == null) {
task = getTask();
}
if (task == null) {
completedAbruptly = true;
break;
}
task.run();
task = null;
}
completedAbruptly = true;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//線程中斷,做一些處理
processWorkerExit(worker, completedAbruptly);
}
}
/**
* 線程中斷,重新開啓線程
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果是因爲線程池關閉導致線程中斷,不做任何處理
if (completedAbruptly)
return;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
workers.remove(w);
} finally {
mainLock.unlock();
}
//判斷線程池是否關閉狀態
if (state == STATE_SHUTDOWN)
return;
addWorker(null);
}
/**
* 從隊列中獲取可用的線程
*/
private Task getTask() throws InterruptedException {
if (state == STATE_SHUTDOWN_NOW)
return null;
if (state == STATE_RUNNING && queue.size() <= 0) {
return null;
}
//如果有閒置時間限制,使用poll方法
//一定時間內還未獲得可用任務,返回null
if (allowThreadTimeOut) {
return queue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
}
//如果隊列中暫時沒有任務,則線程進入阻塞狀態,直到獲取到任務
return queue.take();
}
總結
關鍵的代碼已給出,建議看完整的源碼,克隆下來,在自己電腦跑一跑。
至此一個線程池就完成了,雖然比較簡陋,但重要的東西都有。代碼寫會簡單一些,這是讓大家能更好的理解線程池。完全理解這個代碼時,就可以看java的線程池ThreadPoolExecutor
的源碼了。到時候看源碼應該不會費勁了。