點擊上方“小羅技術筆記”,關注公衆號
第一時間送達實用乾貨
之前發表一篇文章
一個讀者提出了這麼個問題,特此寫此文來說說線程池是如何實現複用的。
線程重用的核心是,我們知道Thread.start()只能調用一次,一旦這個調用結束,則該線程就到了stop狀態,不能再次調用start。那麼要達到複用的目的,則必須從Runnable接口的run()方法上入手,那麼應該這樣設計這個Runnable.run()方法(就叫外面的run()方法):
它本質上是個無限循環,跑的過程中不斷檢查我們是否有新加入的子Runnable對象(就叫內部的runnable:run()吧,它就是用來實現我們自己的任務),有就調一下我們的run(),其實就一個大run()把其它小run()#1,run()#2,...給串聯起來了,基本原理就這麼簡單 不停地處理我們提交的Runnable任務。
僞代碼:
public void run() {
while(true) {
if(tasks available) {
Runnable task = taskqueue.dequeue();
task.run();
} else {
// wait or whatever
}
}
}
大概的實現思路我們粗略的瞭解了一下,那再分析jdk中是如何實現線程複用的
先看ThreadPoolExecutor.execute()方法
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);
}
分析:可以看出:ThreadPoolExecutor.execute()的功能就是:
1、將任務添加至阻塞隊列workQueue,workQueue.offer(command) 2、根據core和maxPool,選擇是否創建Worker,addWorker()
so又回到了Worker的run方法上了!
public void run() {
runWorker(this);
}
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) {
}
}
·······
}
分析:
1、一個大大的while循環,當我們的task不爲空的時候它就永遠在循環,並且會源源不斷的從getTask()獲取新的任務。
2、通過task.run();執行具體的任務。
3、正常情況,只有當所有任務執行完畢纔會停止運行。
我們進一步分析getTask()和task.run()方法。在getTask() 有這麼一個代碼
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
可以發現是從workQueue中獲取task的,所以最終的問題就是看這個變量workQueue是誰的成員變量。
public class ThreadPoolExecutor extends AbstractExecutorService {
private final BlockingQueue<Runnable> workQueue;
···
}
通過以上逐步分析可以發現其實線程複用最核心的一點是,新建一個Worker內部類就會建一個線程,並且會把這個內部類本身傳進去當作任務去執行,這個內部類的run方法裏實現了一個while循環,當任務隊列沒有任務時結束這個循環,則這個線程就結束。
那爲啥拋出異常的線程會被移除掉,再創建一個新線程放到線程池呢?
其實很簡單,源碼裏面 while內部的try/catch 會捕獲任務的異常記錄然後拋出,循環裏面出異常了就會結束這個循環了直接走這個worker的結束方法了,而 processWorkerExit中會默認刪除線程,此時出異常的線程可能還有任務待處理新建一個線程來補上,若有任務就繼續處理剩下的。
長按二維碼關注
點個在看再走唄!
本文分享自微信公衆號 - 小羅技術筆記(javaCodeNote)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。