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