來自讀者的提問,爲什麼要把這個拋出異常的線程移除掉,再創建一個新線程放到線程池?

點擊上方“小羅技術筆記”,關注公衆號

第一時間送達實用乾貨

之前發表一篇文章

70%人答不全!線程池中的一個線程異常了會被怎麼處理?

一個讀者提出了這麼個問題,特此寫此文來說說線程池是如何實現複用的。


     線程重用的核心是,我們知道Thread.start()只能調用一次,一旦這個調用結束,則該線程就到了stop狀態,不能再次調用start。那麼要達到複用的目的,則必須從Runnable接口的run()方法上入手,那麼應該這樣設計這個Runnable.run()方法(就叫外面的run()方法):

      它本質上是個無限循環,跑的過程中不斷檢查我們是否有新加入的子Runnable對象(就叫內部的runnable:run()吧,它就是用來實現我們自己的任務),有就調一下我們的run(),其實就一個大run()把其它小run()#1,run()#2,...給串聯起來了,基本原理就這麼簡單 不停地處理我們提交的Runnable任務。

     
   
   
   
  1. 僞代碼:

  2. public void run() {

  3.    while(true) {

  4.        if(tasks available) {

  5.           Runnable task = taskqueue.dequeue();

  6.           task.run();

  7.        } else {

  8.           // wait or whatever

  9.        }

  10.    }

  11. }

大概的實現思路我們粗略的瞭解了一下,那再分析jdk中是如何實現線程複用的

先看ThreadPoolExecutor.execute()方法

     
   
   
   
  1.    public void execute(Runnable command) {

  2.        if (command == null)

  3.            throw new NullPointerException();

  4.        int c = ctl.get();

  5.        if (workerCountOf(c) < corePoolSize) {

  6.            if (addWorker(command, true))

  7.                return;

  8.            c = ctl.get();

  9.        }

  10.        if (isRunning(c) && workQueue.offer(command)) {

  11.            int recheck = ctl.get();

  12.            if (! isRunning(recheck) && remove(command))

  13.                reject(command);

  14.            else if (workerCountOf(recheck) == 0)

  15.                addWorker(null, false);

  16.        }

  17.        else if (!addWorker(command, false))

  18.            reject(command);

  19.    }

分析:可以看出:ThreadPoolExecutor.execute()的功能就是:

1、將任務添加至阻塞隊列workQueue,workQueue.offer(command) 2、根據core和maxPool,選擇是否創建Worker,addWorker()

因此,線程複用的實現應該在worker中,打開addWorker()方法觀察,源代碼太長了這裏我只寫分析結果 addworker分爲兩部分:
1 、創建worker,
2、啓動worker
 規則校驗: 與core和maxPool數量的規則相同, 創建worker: 獲取ThreadLocal的全局鎖。 安全的創建Worker執行t.start();

so又回到了Worker的run方法上了!

     
   
   
   
  1. public void run() {

  2.            runWorker(this);

  3. }

  4. final void runWorker(Worker w) {

  5.        Thread wt = Thread.currentThread();

  6.        Runnable task = w.firstTask;

  7.        w.firstTask = null;

  8.        w.unlock(); // allow interrupts

  9.        boolean completedAbruptly = true;

  10.        try {

  11.            while (task != null || (task = getTask()) != null) {

  12.            }

  13.          }

  14.          ·······

  15.        }

分析:

1、一個大大的while循環,當我們的task不爲空的時候它就永遠在循環,並且會源源不斷的從getTask()獲取新的任務。

2、通過task.run();執行具體的任務。

3、正常情況,只有當所有任務執行完畢纔會停止運行。

我們進一步分析getTask()和task.run()方法。在getTask() 有這麼一個代碼

     
   
   
   
  1. Runnable r = timed ?

  2.                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :

  3.                    workQueue.take();

  4.                if (r != null)

  5.                    return r;

  6.                timedOut = true;

可以發現是從workQueue中獲取task的,所以最終的問題就是看這個變量workQueue是誰的成員變量。

     
   
   
   
  1. public class ThreadPoolExecutor extends AbstractExecutorService {

  2.    private final BlockingQueue<Runnable> workQueue;

  3.   ···

  4. }

    通過以上逐步分析可以發現其實線程複用最核心的一點是,新建一個Worker內部類就會建一個線程,並且會把這個內部類本身傳進去當作任務去執行,這個內部類的run方法裏實現了一個while循環,當任務隊列沒有任務時結束這個循環,則這個線程就結束。

那爲啥拋出異常的線程會被移除掉,再創建一個新線程放到線程池呢?

    其實很簡單,源碼裏面 while內部的try/catch 會捕獲任務的異常記錄然後拋出,循環裏面出異常了就會結束這個循環了直接走這個worker的結束方法了,而 processWorkerExit中會默認刪除線程,此時出異常的線程可能還有任務待處理新建一個線程來補上,若有任務就繼續處理剩下的。



長按二維碼關注

點個在看再走唄!

本文分享自微信公衆號 - 小羅技術筆記(javaCodeNote)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章