前言
最近看到crossoverJie的一篇文章:一個線程罷工的詭異事件
首先感謝原作者的分享,自己獲益匪淺。然后是回想到自己的一次面試經歷,面試官提問了線程池中的線程出現了異常該怎樣捕獲?會導致什么樣的問題?
示例代碼
public class ThreadPoolException {private final static Logger LOGGER = LoggerFactory.getLogger(ThreadPoolException.class);public static void main(String[] args) throws InterruptedException {ExecutorService execute = new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());execute.execute(new Runnable() {@Overridepublic void run() {LOGGER.info("=====11=======");}});TimeUnit.SECONDS.sleep(5);execute.execute(new Run1());}private static class Run1 implements Runnable {@Overridepublic void run() {int count = 0;while (true) {count++;LOGGER.info("-------222-------------{}", count);if (count == 10) {System.out.println(1 / 0);try {} catch (Exception e) {LOGGER.error("Exception",e);}}if (count == 20) {LOGGER.info("count={}", count);break;}}}}
}
上面的代碼是原作者本地調試的一個代碼,這里我也大致交代下情形:
- 首先是啟動main方法看最終執行現象
這里直接拋異常了,by zero。看到底層是ThreadPoolExecutor 1149行拋出的。
查看線程dump,發現線程池中的線程此時處于WAITING狀態
- 源碼追蹤
這里就需要弄清楚為何會出現WAITING狀態,所以我們需要一步步追蹤源碼。
我們可以在拋異常的地方打斷點,然后一步步跟蹤:
在執行1149行代碼由于拋了異常,所以繼續執行finally中processWorkerExit方法:
processWorkerExit中主要做了兩件事,worker remover和addWorker。線程池中的任務都會被包裝為一個內部 Worker 對象執行。不清楚的可以參考:Java并發之線程池ThreadPoolExecutor源碼學習
最后會執行addWorker,緊接著我們繼續往addWorker中去跟,看看里面做了什么操作:
addWorker里面是重新new Worker(), 然后執行worker.start(), 接著我們看下Worker中的start方法:
因為Woker是實現Runnable接口的,所以會執行其run方法,接著往runWorker方法跟蹤:
因為此時的Worker是上一步重新new出來的,所以其中的task為空,這時需要繼續跟蹤getTask()方法:
此時因為線程池的隊列中并沒有任務,所以這里執行take會一直阻塞,也就有了最開始的那個WAITING的狀態了。
到了這里一切都很明了了,源碼面前任何妖魔鬼怪都無法藏匿,所以但我們使用線程池的時候一定要注意一異常的捕獲和處理。
下一章來詳細解讀一下如何捕獲線程池中的異常。
由于本人水平有限,文章中如果有不嚴謹的地方還請提出來,愿聞其詳。