前言
前面我們在講併發工具類的時候,多次提到線程池,今天我們就來走進線程池的旅地,首先我們先不講線程池框架Executors,我們今天先來介紹如何自己定義一個線程池,是不是已經迫不及待了,那麼就讓我們開啓今天的旅途吧。
什麼是線程池?
線程池可以理解爲一個專門管理線程生命週期的池子,裏面的線程都可以由這個池子本身來調度,使用線程池有哪些好處呢?
-
降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗
-
提高響應速度:當任務到達時,任務可以不需要的等到線程創建就能立即執行
-
提高線程的可管理性:使用線程池可以對線程進行統一的分配,調優和監控
線程池的實現原理
首先我們看下面這張圖,對着圖進行分析:
過程分析:當任務到達時,首先會判斷核心線程池是否還有空閒線程,如果有則創建一個新的工作線程執行任務,如果沒有空閒線程,則說明核心線程池已滿,進行工作隊列是否滿的判斷,如果沒有滿,則將任務存放在等待隊列中,如果工作隊列也滿了,則再去判斷線程池是否滿,如果沒有滿,則新建一個線程來執行任務,否則採取拒絕策略;
下面我們來對核心線程池提交任務過程進行分析,這是線程池的核心角色,首先我們看下源碼:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果工作線程數量小於核心線程數,則創建一個新的工作線程執行任務
if (workerCountOf(c) < corePoolSize) {
// 創建工作線程成功,則直接返回
if (addWorker(command, true))
return;
c = ctl.get();
}
// 工作線程>核心線程數或者工作線程創建失敗時,將任務放入等待隊列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果等待隊列滿了,並且工作線程數量大於線程池總數量,則採取拒絕策略
else if (!addWorker(command, false))
reject(command);
}
我們會發現,當可以分配線程來執行任務時,我們總是新建一個工作線程Worker來執行任務,我們來了解下Worker特殊的地方,工作線程不僅會執行當前的任務,而且當前任務執行完畢之後,還會去等待隊列中獲取任務來執行,通過工作線程的源碼可以得知這一點:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
上面我們講到的工作隊列,我們之前學到過隊列有很多種,例如阻塞隊列和非阻塞隊列,有界隊列和無界隊列,那麼在我們線程池中當使用不同的工作工作隊列又會有什麼區別呢?
-
使用有界隊列時:有新的任務需要執行時,如果線程池實際線程數小於corePoolSize,則優先創建線程,如果大於corePoolSize,則會將任務先加入到隊列,等待執行,如果隊列也滿了,則在總線程數不大於maximumPoolSize時,先創建新的線程,如果線程數大於了maximumPoolSize則執行拒絕策略,或者其他自己自定義的處理策略;
-
使用無界隊列時:LinkedBlockingQueue,與有界隊列相比,除非系統資源被耗盡,否則無界隊列的任務隊列不存在任務入隊列失敗的情況,當有新任務到來,系統的線程數小於corePoolSize時,則新建線程執行任務,當線程數量達到corePoolSize值後,則線程不會繼續新建,如果此時還持續有任務進來,而沒有空閒的線程資源,則任務會進入隊列排隊等待,若任務創建和處理的速度差異很大,則無界隊列會保持快速增長,直到資源耗盡內存,任務會一直堆積,直到內存滿了,這種情況永遠不會有有界隊列中工作線程和線程池總數的比較過程;
創建線程池:自定義線程池也是通過ThreadPoolExecutor(線程池執行器)來實現,構造方法如下
public ThreadPoolExecutor(int corePoolSize, //核心線程數--線程池初始化創建的線程數量
int maximumPoolSize, // 最大線程數,線程池中能創建的最大線程數
long keepAliveTime, // 線程空閒等待時間
TimeUnit unit, // 線程空閒等待時間的單位
BlockingQueue<Runnable> workQueue, // 存放待執行任務的等待隊列
RejectedExecutionHandler handler // 拒絕任務的處理策略) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
特別說明,拒絕策略有如下幾種:
-
AbortPolicy策略:該策略直接拋出異常,阻止系統工作
-
CallerRunsPolicy策略:只要線程池未關閉,該策略直接在調用者線程中運行當前被丟棄的任務。顯然這樣不會真的丟棄任務,但是,調用者線程性能可能急劇下降
-
DiscardOledestPolicy策略:丟棄最老的一個請求任務,也就是丟棄一個即將被執行的任務,並嘗試再次提交當前任務
-
DiscardPolicy策略:不處理,直接丟棄掉
向線程池提交任務:execute方法在上面已經介紹過了,這裏就不重複介紹了
關閉線程池:關閉線程池有下面2個方法
// 將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
// 調用線程中斷方法
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
// 遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。
// shutdownNow會首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,
// 並返回等待執行任務的列表,如果任務不一定要執行完,可以使用此方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}