線程池基本介紹
爲什麼要使用線程池
對於系統和服務器來說,創建和銷燬一個線程所需要消耗的時間和資源可能比處理相關業務所消耗的時間和資源還要多還要久,不僅如此,計算機爲了提高運算效率,CPU會在衆多的線程之間不斷的進行線程間的切換,如果線程可以隨意的創建,過多的線程之間進行頻繁的切換也會佔用大量的內存和資源
創建線程池的兩種方式
- 通過Executors創建線程池,這是官方較爲推薦的一種方式,它們均爲大多數使用場景預定義了設置
創建線程池強烈建議程序員使用較爲方便的 Executors 工廠方法 Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)Executors.newFixedThreadPool(int)(固定大小線程池) Executors.newSingleThreadExecutor()(單個後臺線程)
- 通過構造方法創建自定義的線程池
ExecutorService executorService = new ThreadPoolExecutor(5, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
線程池處理任務流程
當提交新任務到線程池當中,線程池主要做了以下幾個判斷
- 檢查線程池當中的線程數是否已經超過了核心線程數,如果沒有,則創建新的線程對任務進行處理
- 如果線程池當中的線程數量已經達到了設定的核心線程數的數量,則將當前任務加入到任務隊列當中,任務隊列中的任務由空閒線程執行
- 如果任務隊列中的任務數量已經足夠了,線程池則會判斷當前線程池中的線程數量是否已經超出最大線程數,如果沒有超出最大線程數,則會創建一個普通線程去處理提交的這個任務
需要c/c++ Linux服務器高階知識、電子書籍、視頻資料的朋友請加羣
知識點有C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等等。
分享一個實現線程池的視頻:
150行代碼,手把手寫完線程池(完整版)|項目實戰|消息隊列|數據庫
源碼解析
創建一個線程池
通過自定義的方式構造一個線程池,調用了構造線程池的構造方法,創建一個ThreadPoolExecutor對象,並返回ExecutorService類型
ExecutorService executorService = new ThreadPoolExecutor(5, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
通過上述構造方法,我們來對ThreadPoolExecutor類進行解析,它也會線程池處理線程問題的源碼
ThreadPoolExecutor基本構成
public class ThreadPoolExecutor extends AbstractExecutorService {
//省略
...
}
public abstract class AbstractExecutorService implements ExecutorService {
//省略
...
}
public interface ExecutorService extends Executor {
//省略
...
}
public interface Executor {
void execute(Runnable command);
}
此處我將ThreadPoolExecutor類的繼承關係拆開了,ThreadPoolExecutor類繼承AbstractExecutorService,AbstractExecutorService是executorService接口的實現類,executorService是executor的子接口
- executorService中的方法主要是對線程池中提交的任務進行控制管理,該類可以對任務進行提交和關閉等操作
- AbstractExecutorService是線程池類的實現類,裏面主要是executorService方法的實現
- ThreadPoolExecutor類主要是對提交的線程進行控制,管理,執行
線程池狀態源碼基本變量組成及解析
//通過ctl獲取runState和workerCount狀態
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;//
private static final int CAPACITY = (1 << COUNT_BITS) - 1;//最大線程數容量
private static final int RUNNING = -1 << COUNT_BITS;//線程池可以接受新任務
private static final int SHUTDOWN = 0 << COUNT_BITS;//線程池不在接受新任務(當工作線程大於等於最大線程的時候)
private static final int STOP = 1 << COUNT_BITS;//線程池不在接受新任務,不在執行隊列中的任務
private static final int TIDYING = 2 << COUNT_BITS;//線程池中所有任務均終止
private static final int TERMINATED = 3 << COUNT_BITS;//線程池徹底終止
// ctl操作
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
//當需要操作workers對象的時候,需要獲取鎖資源
private final ReentrantLock mainLock = new ReentrantLock();
//工作線程集合,包含池中所有的工作線程,只能在拿着mainLock時才能訪問。
//Worker 引用存在workers集合裏面
private final HashSet<Worker> workers = new HashSet<Worker>();
變量ctl是非常特殊的一個變量,他是用來表示線程池狀態和當前線程數量的一個變量,此處申明瞭final類型,說明AtomicInteger類不可以被繼承,需要注意的是AtomicInteger是一個類 ctl是一個原子整數,利用高低位包裝瞭如下兩個概念
- runState:線程池運行狀態,佔據高三位,主要有RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED這幾個狀態
- workerCount:線程池中當前活動的線程數量,佔據第29位
集合workers用來存放被創建的工作線程,包含線程池中的所有工作線程,只有在拿到mainLock的時候才能進行訪問
ThreadPoolExecutor執行入口方法
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();
}
//當線程池狀態是運行狀態且成功加入工作隊列中
//當workerCountOf(c)的數量大於等於corePoolSize的時候
// 此處需要注意,在任務數超過核心線程數的時候,線程池將任務添加進隊列中由核心線程來處理,在大多數時候,線程池中的線程數維持在覈心線程數之內不會額外創建線程
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();//重新獲取線程池運行狀態
if (! isRunning(recheck) && remove(command))//如果運行狀態不是可運行,則移除當前任務
reject(command);//關閉當前任務
else if (workerCountOf(recheck) == 0)//當線程池中的線程數是0時,此時任務已經加入到workerQueue中了,此處的含義應該是加入一個空的線程,目的是消費workerQueue中的任務
addWorker(null, false);
}
//如果隊列也已經滿了,則創建一個線程去執行任務,如果工作線程數量超過了最大線程數量,則執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
該方法接收需要進行處理的任務,是一個實現了Runnable接口的對象,在未來的某個時候執行給出的任務,這個任務由一個新線程或是線程池中的線程進行執行, 如果這個任務不能被正確執行,不管什麼原因,都提交給RejectedExecutionHandler去處理 在執行任務的時候,主要有以下幾種情況
- 當前線程數量小於核心線程數,此時將會創建新的線程去執行任務
- 當前線程數量大於核心線程數的時候,將當前任務加入到工作隊列中去,並再次檢查線程池運行狀態,如果當前任務數量爲0則創建一個線程去處理任務
- 如果工作隊列也已經滿了,則創建一個線程去執行該任務,一般這種線程在keepAliveTime到期後就會自動銷燬
此處需要注意的是當任務被加入到任務隊列中後,當發現可用於執行任務的線程數爲0時,會構造一個不包含任務的工作線程,該線程的作用是去處理之前被放入到工作隊列中的任務的 此處提出一個問題,對於線程池來說,如果線程池中的線程數沒有到達核心線程數的話,則創建核心線程,如果超過了,創建的線程就不是核心線程了,那麼核心線程和非核心線程的區別在哪裏?對於這個問題,下面在解析getTask源碼的時候會進行解釋,線程池對於核心線程和非核心線程的不同處理
構建Worker並執行firstTask
private boolean addWorker(Runnable firstTask, boolean core) {
//第一階段,主要作用是檢查,檢查線程池運行狀態和活動線程數量是否符合要求,否則返回false
retry:// continue/break跳出標記位,其中break表示要跳過這個標記的循環,continue表示從這個標記的循環繼續執行
for (;;) {
int c = ctl.get();//獲取當前線程池狀態和線程數量
int rs = runStateOf(c);//獲取線程池運行狀態
// 檢查當前線程池狀態,如果狀態大於等於SHUTDOWN(說明線程池已經停止)
// 且線程池已關閉且任務爲空且工作隊列不爲空,返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; //返回false
//該循環主要是檢測線程池中活動線程數量是否在合理的範圍之內,否則返回false
//如果活動線程數在規定的範圍之內,則更新活動線程數並跳出循環執行work進行創建線程
for (;;) {
int wc = workerCountOf(c); //獲取工作線程的數量
//如果工作線程數量大於活動線程數或者大於核心線程數
//core爲true則是核心線程數
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false; //返回false
//比較當前值是否和c相同,如果相同則c+1並直接跳出大循環,執行work進行線程的創建
if (compareAndIncrementWorkerCount(c))
break retry;//退出循環
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)//如果當前的線程運行狀態不是rs(方法開頭獲取的運行狀態)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//第二階段:創建Worker對象,並啓動線程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//根據你給的task創建worker對象
//Worker類實現了Runnable接口,worker對象實現run方法
w = new Worker(firstTask);
//線程t是用來處理worker對象中任務的線程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//rs < SHUTDOWN表示線程池處於可運行狀態
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // t爲新創建的線程,在workQueue運行狀態下如果已經啓動需要拋出異常
throw new IllegalThreadStateException();
//將創建的線程添加到線程池的集合中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;//此處的作用是啓動該線程
}
} finally {
mainLock.unlock();
}
if (workerAdded) {//爲true時啓動線程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)//如果worker創建失敗,則加入addWorkerFailed
addWorkerFailed(w);
}
return workerStarted;
}
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
//AQS獨佔鎖模板方法,讀者有興趣也可以看下,此處省略
...
}
該方法是線程池處理提交任務的核心方法,線程池啓動線程的地方,實現run方法,主要是分爲兩個階段
- 第一階段,主要作用是檢查,檢查線程池運行狀態和活動線程數量相關問題
- 第二階段,創建worker對象,並啓動線程執行任務
第二階段是進行工作線程的創建,創建工作線程,首先是建立一個Worker對象,由源碼可知,Worker繼承了AQS,實現了runnable,內部封裝了待處理的任務和處理這個任務所需的線程,通過Worker內部的run方法,可以實現具體任務的邏輯,需要注意的是,Worker是繼承了AQS的,且是獨佔鎖,在Worker的run方法中調用了runWorker方法,在runWorker方法中,線程只有在獲取到鎖資源的情況下,纔可以處理Worker對象中的被封裝的任務
在構建完Worker對象後,後面的代碼看着挺多,實際上就是爲執行worker對象中的線程做準備
- 首先獲取鎖資源,這裏獲取鎖的原因是因爲要將可執行worker對象放入工作集合中,當線程池的狀態是可運行但是被構建的線程已經執行,則拋出異常,因爲此時線程才被創建還沒有被運行,如果運行了,則要拋出
- 此時將當前被創建的worker對象加入到集合當中,並記錄當前線程池最大線程數,釋放鎖,並執行線程方法
Worker中run方法的實現類
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//獲取當前線程,即爲正在執行的,創建了worker的線程
Runnable task = w.firstTask;//獲取封裝進worker中的task任務
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//1:如果創建該worker時傳遞的task不爲空(當前worker對應的任務還沒有執行),則去執行task
//2:如果worker中的task已經執行完了,則去檢查是否還有task任務沒有執行,如果有則獲取workQueue的task並執行
while (task != null || (task = getTask()) != null) {
//Worker繼承AQS,目的是想使用獨佔鎖來表示線程是否正在執行任務
w.lock();
// 如果線程池停止了,確保線程被打斷
// 如果沒有被打斷,第二種情況要求在清除中斷時去處理shutdownNow方法的競爭
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();//中斷該線程
try {
beforeExecute(wt, task);//這是ThreadPoolExecutor提供給子類的擴展方法
Throwable thrown = null;
try {
task.run();//futureTask執行的地方
} 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;//任務變量變爲null
w.completedTasks++;//之前線程的數量加1
w.unlock();
}
}
completedAbruptly = false;
} finally {
//銷燬當前線程
processWorkerExit(w, completedAbruptly);
}
}
執行worker中的任務,也就是我們提交給線程池中任務的邏輯代碼,此時,則完成了線程池對當前任務的處理流程 如果任務執行完成了,則繼續執行workQueue中的任務,如果worker中的任務爲空,則單獨執行隊列中的任務,此處保證了線程在還有任務沒有完成的情況下,不斷的從任務隊列中獲取任務去處理,避免創建無謂的線程,此處需要注意的是getTask()方法,通過該方法,線程不斷的從任務隊列中獲取任務
獲取workQueue中的任務
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);//獲取當前線程池的運行狀態
// 如果線程池當前狀態已經停止且隊列是空時返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();//重新設置當前線程池狀態,內部通過cas方式實現狀態的修改
return null;//返回
}
int wc = workerCountOf(c);//獲取工作線程的數量
// 當前線程數是否大於核心線程數,是則返回true或者allowCoreThreadTimeOut被設置爲true時,
// 在keepAliveTime到了之後,將會銷燬非核心線程或是allowCoreThreadTimeOut被設置爲true的核心線程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//當前線程數大於最大線程數或者(當前線程數大於核心線程數且timedOut過)且(wc > 1 || workQueue.isEmpty())
//return將會返回null,程序銷燬當前線程
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))//cas減少任務數量
return null;
continue;//繼續進行循環
}
try {
//獲取並從workQueue中移除該任務
//如果false則從隊列中獲取任務,如果爲true則表示當在時間keepAliveTime內沒有獲取到任務,此時移除該任務。此時任務r爲null,並將timedOut設置爲true,用於上面的判斷條件
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)//如果獲取的任務不爲null則返回該對象,設置timeout爲true,目的是移除當前線程
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
獲取workQueue中的任務,獲取任務後直接返回任務,沒有獲取成功則會返回null,以下幾種情況將會返回null,如果返回null且當前線程是非核心線程,則會直接銷燬當且線程,如果當前線程是核心線程,如果設置allowCoreThreadTimeOut爲true,核心線程也會被銷燬
- 線程池已經停止工作了
- 隊列已經空了
- 如果當前線程爲非核心線程或allowCoreThreadTimeOut,在keepAliveTime內沒有獲取到任務
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))//cas減少任務數量
return null;
continue;//繼續進行循環
}
在上面的代碼中,當timed爲true的時候,說明當前線程只有兩種身份,非核心線程或是允許被銷燬的核心線程 timedOut默認爲false,當在keepaliveTime的時間內沒有獲取到任務,將會被標註爲true 判斷中當線程池中線程數大於所設定的線程數或者timed爲true且timedOut爲true的時候滿足其中之一,當工作線程數量大於1或是工作隊列是空的情況下,就會返回null
回到文章一開始提出的一個問題,核心線程和非核心線程的區別 如果getTask()返回null的話,則直接銷燬非核心線程,但是核心線程不會被銷燬(如果核心線程做了設置也會進行銷燬)
至此,線程池源碼對於我們提交任務的處理的解析就全部完成了,如果文章有不對的地方,歡迎指正