線程池是我們常用到的功能之一,顧名思義,線程池就是存放線程的緩衝池,在一般的程序設計中,爲了提高性能或處理較爲複雜耗時的操作,我們一般開多個線程。線程池的目的就是管理這些線程,控制其生命週期以及任務分配等。
在寫較深入的線程程序之前,我們一般會問,我們的任務(實現Runnable或Callable接口的類對象)被提交後是如何執行的,線程是如何被啓動及終止的,線程池中的線程數是固定的嗎,是一個線程運行一個任務還是一個線程運行多個任務的呢?
下面我們結合jdk的源代碼來分析一下線程池的實現模型。
1. 生命週期
線程池的生命週期一共存在四種運行狀態標誌,分別爲RUNNING、SHUTDOWN、STOP、TERMINATED,定義如下:
RUNNING:接受新任務並處理已排隊(提交的任務是被放在一隊列中)任務
SHUTDOWN:不接受新任務,但是處理已排隊任務
STOP:不接受新任務,不處理已排隊任務,並終止正在運行的任務
TERMINATED:與STOP相同,並加上所有任務均已終止
下面是各個狀態之間的轉換:
RUNNING -> SHUTDOWN:調用shutdown()函數
(RUNNING or SHUTDOWN) -> STOP:調用shutdownNow()函數
SHUTDOWN -> TERMINATED:當任務隊列和線程池均爲空
STOP -> TERMINATED:當線程池爲空
這個要注意一下,在調用執行器提交完任務之後,一定要調用執行器的shutdown()或者shutdownNow()方法來終止線程池,不然的話即使所有任務處理完畢,虛擬機也不會退出。
2. 調度過程
無論怎麼寫程序,線程池也是有其執行器創建的,並在其構造函數中確定了線程池大小,下面是ThreadPoolExecutor的構造函數:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {…}
corePoolSize:核心線程池的大小,即即使線程空閒,也會保持這個數目
maximumPoolSize:線程池的最大大小,一般是Integer.MAX_VALUE,corePoolSize必須小於或等於這個數
workQueue:任務被執行之前放在這個隊列中,線程要執行任務時從這個隊列中取。注意BlockingQueue已經內置了同步機制,隊列滿或空均會使其處於對待狀態。
其他參數不再說明。
通過上面大家可以看到線程和任務是分開存放管理的,這一點是理解線程池的關鍵。
下面我們通過一個函數看一下線程是如何被創建和啓動的:
private void delayedExecute(Runnable command) {
//如果運行狀態爲SHUTDOWN,拒絕此命令
if (isShutdown()) {
reject(command);
return;
}
//如果當前線程數小於核心線程數,則創建並啓動一個線程
if (getPoolSize() < getCorePoolSize())
prestartCoreThread();
//把任務加入到任務隊列中
super.getQueue().add(command);
}
那麼線程是如何執行任務的呢:
總體來說,提交的任務按照提交先後順序被放入任務隊列,線程啓動後從隊列中取出任務執行,任務被取出後即被從隊列中刪除。由於操作系統的調度和每個任務的運算量不同,因此單個線程取出的任務並沒有順序,但是一定是隊列的頭,每個線程都是平等的,不允許取頭後面的任務。
下面是具體代碼:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
//注意下面這個while循環,只要有任務就不會被終止,
//getTask()是取出任務隊列的頭,如果隊列爲空,進入阻塞狀態
//runTask()是運行這個任務
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
更多的具體細節,請大家看ThreadPoolExecutor和ScheduledThreadPoolExecutor類 的源代碼,兩者都位於java.util.concurrent包中。