線程池?cái)?shù)據(jù)結(jié)構(gòu)與線程構(gòu)造方法
由于已經(jīng)看到了ThreadPoolExecutor的源碼,因此很容易就看到了ThreadPoolExecutor線程池的數(shù)據(jù)結(jié)構(gòu)。圖1描述了這種數(shù)據(jù)結(jié)構(gòu)。
圖1 ThreadPoolExecutor 數(shù)據(jù)結(jié)構(gòu)
其實(shí),即使沒(méi)有上述圖形描述ThreadPoolExecutor的數(shù)據(jù)結(jié)構(gòu),我們根據(jù)線程池的要求也很能夠猜測(cè)出其數(shù)據(jù)結(jié)構(gòu)出來(lái)。
- 線程池需要支持多個(gè)線程并發(fā)執(zhí)行,因此有一個(gè)線程集合Collection<Thread>來(lái)執(zhí)行線程任務(wù);
- 涉及任務(wù)的異步執(zhí)行,因此需要有一個(gè)集合來(lái)緩存任務(wù)隊(duì)列Collection<Runnable>;
- 很顯然在多個(gè)線程之間協(xié)調(diào)多個(gè)任務(wù),那么就需要一個(gè)線程安全的任務(wù)集合,同時(shí)還需要支持阻塞、超時(shí)操作,那么BlockingQueue是必不可少的;
- 既然是線程池,出發(fā)點(diǎn)就是提高系統(tǒng)性能同時(shí)降低資源消耗,那么線程池的大小就有限制,因此需要有一個(gè)核心線程池大小(線程個(gè)數(shù))和一個(gè)最大線程池大小(線程個(gè)數(shù)),有一個(gè)計(jì)數(shù)用來(lái)描述當(dāng)前線程池大小;
- 如果是有限的線程池大小,那么長(zhǎng)時(shí)間不使用的線程資源就應(yīng)該銷(xiāo)毀掉,這樣就需要一個(gè)線程空閑時(shí)間的計(jì)數(shù)來(lái)描述線程何時(shí)被銷(xiāo)毀;
- 前面描述過(guò)線程池也是有生命周期的,因此需要有一個(gè)狀態(tài)來(lái)描述線程池當(dāng)前的運(yùn)行狀態(tài);
- 線程池的任務(wù)隊(duì)列如果有邊界,那么就需要有一個(gè)任務(wù)拒絕策略來(lái)處理過(guò)多的任務(wù),同時(shí)在線程池的銷(xiāo)毀階段也需要有一個(gè)任務(wù)拒絕策略來(lái)處理新加入的任務(wù);
- 上面種的線程池大小、線程空閑實(shí)際那、線程池運(yùn)行狀態(tài)等等狀態(tài)改變都不是線程安全的,因此需要有一個(gè)全局的鎖(mainLock)來(lái)協(xié)調(diào)這些競(jìng)爭(zhēng)資源;
- 除了以上數(shù)據(jù)結(jié)構(gòu)以外,ThreadPoolExecutor還有一些狀態(tài)用來(lái)描述線程池的運(yùn)行計(jì)數(shù),例如線程池運(yùn)行的任務(wù)數(shù)、曾經(jīng)達(dá)到的最大線程數(shù),主要用于調(diào)試和性能分析。
對(duì)于ThreadPoolExecutor而言,一個(gè)線程就是一個(gè)Worker對(duì)象,它與一個(gè)線程綁定,當(dāng)Worker執(zhí)行完畢就是線程執(zhí)行完畢,這個(gè)在后面詳細(xì)討論線程池中線程的運(yùn)行方式。
既然是線程池,那么就首先研究下線程的構(gòu)造方法。
Thread newThread(Runnable r);
}
ThreadPoolExecutor使用一個(gè)線程工廠來(lái)構(gòu)造線程。線程池都是提交一個(gè)任務(wù)Runnable,然后在某一個(gè)線程Thread中執(zhí)行,ThreadFactory 負(fù)責(zé)如何創(chuàng)建一個(gè)新線程。
在J.U.C中有一個(gè)通用的線程工廠java.util.concurrent.Executors.DefaultThreadFactory,它的構(gòu)造方式如下:
static final AtomicInteger poolNumber = new AtomicInteger(1);
final ThreadGroup group;
final AtomicInteger threadNumber = new AtomicInteger(1);
final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null)? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
在這個(gè)線程工廠中,同一個(gè)線程池的所有線程屬于同一個(gè)線程組,也就是創(chuàng)建線程池的那個(gè)線程組,同時(shí)線程池的名稱都是“pool-<poolNum>-thread-<threadNum>”,其中poolNum是線程池的數(shù)量序號(hào),threadNum是此線程池中的線程數(shù)量序號(hào)。這樣如果使用jstack的話很容易就看到了系統(tǒng)中線程池的數(shù)量和線程池中線程的數(shù)量。另外對(duì)于線程池中的所有線程默認(rèn)都轉(zhuǎn)換為非后臺(tái)線程,這樣主線程退出時(shí)不會(huì)直接退出JVM,而是等待線程池結(jié)束。還有一點(diǎn)就是默認(rèn)將線程池中的所有線程都調(diào)為同一個(gè)級(jí)別,這樣在操作系統(tǒng)角度來(lái)看所有系統(tǒng)都是公平的,不會(huì)導(dǎo)致競(jìng)爭(zhēng)堆積。
線程池中線程生命周期
一個(gè)線程Worker被構(gòu)造出來(lái)以后就開(kāi)始處于運(yùn)行狀態(tài)。以下是一個(gè)線程執(zhí)行的簡(jiǎn)版邏輯。
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
task.run();
} finally {
runLock.unlock();
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
}
當(dāng)提交一個(gè)任務(wù)時(shí),如果需要?jiǎng)?chuàng)建一個(gè)線程(何時(shí)需要在下一節(jié)中探討)時(shí),就調(diào)用線程工廠創(chuàng)建一個(gè)線程,同時(shí)將線程綁定到Worker工作隊(duì)列中。需要說(shuō)明的是,Worker隊(duì)列構(gòu)造的時(shí)候帶著一個(gè)任務(wù)Runnable,因此Worker創(chuàng)建時(shí)總是綁定著一個(gè)待執(zhí)行任務(wù)。換句話說(shuō),創(chuàng)建線程的前提是有必要?jiǎng)?chuàng)建線程(任務(wù)數(shù)已經(jīng)超出了線程或者強(qiáng)制創(chuàng)建新的線程,至于為何強(qiáng)制創(chuàng)建新的線程后面章節(jié)會(huì)具體分析),不會(huì)無(wú)緣無(wú)故創(chuàng)建一堆空閑線程等著任務(wù)。這是節(jié)省資源的一種方式。
一旦線程池啟動(dòng)線程后(調(diào)用線程run())方法,那么線程工作隊(duì)列Worker就從第1個(gè)任務(wù)開(kāi)始執(zhí)行(這時(shí)候發(fā)現(xiàn)構(gòu)造Worker時(shí)傳遞一個(gè)任務(wù)的好處了),一旦第1個(gè)任務(wù)執(zhí)行完畢,就從線程池的任務(wù)隊(duì)列中取出下一個(gè)任務(wù)進(jìn)行執(zhí)行。循環(huán)如此,直到線程池被關(guān)閉或者任務(wù)拋出了一個(gè)RuntimeException。
由此可見(jiàn),線程池的基本原理其實(shí)也很簡(jiǎn)單,無(wú)非預(yù)先啟動(dòng)一些線程,線程進(jìn)入死循環(huán)狀態(tài),每次從任務(wù)隊(duì)列中獲取一個(gè)任務(wù)進(jìn)行執(zhí)行,直到線程池被關(guān)閉。如果某個(gè)線程因?yàn)閳?zhí)行某個(gè)任務(wù)發(fā)生異常而終止,那么重新創(chuàng)建一個(gè)新的線程而已。如此反復(fù)。
其實(shí),線程池原理看起來(lái)簡(jiǎn)單,但是復(fù)雜的是各種策略,例如何時(shí)該啟動(dòng)一個(gè)線程,何時(shí)該終止、掛起、喚醒一個(gè)線程,任務(wù)隊(duì)列的阻塞與超時(shí),線程池的生命周期以及任務(wù)拒絕策略等等。下一節(jié)將研究這些策略問(wèn)題。