文章目錄
現象
線上發短信、郵箱驗證碼 的時候超時
結論
-
SocketInputStream.socketRead0導致線程阻塞,阻塞後佔用了線程池的線程。多次阻塞後最終佔用了全部的core線程。新提交的任務只能入隊,沒有線程來處理。
由於 socket.read佔用了corePoolSize 個 線程池的工作線程worker.thread , 這裏一共有10個,全都阻塞了。而execute提交一個runnable的時候, 在達到corePoolSize後, 會將其放入workQueue中。直到workQueue滿。
新的任務只能入隊(enQueue),不能被消費。
所以 futureTask.get 一直超時。
-
futureTask.get(timeout,timeunit)不會導致線程池的工作線程異常。工作線程會繼續執行。
相關代碼
sendVcWorkerThreadPool是ThreadPoolExecutor的子類WorkerThreadPool
WorkerThreadPool
這裏workQueue本身是一個優先隊列,這裏會無限擴容
ps:由於無限擴容, 這裏maxinumPoolSize是無效的
先查找相關調用鏈的底層日誌,發現根本沒有調用底層方法。
而其他應用能調用到該底層,一直在輸出日誌,說明是execute本身有問題。
查看堆棧:
搜索AsyncWorker(ps:自定義的線程池一定要重命名,找問題的時候方便),發現該線程池的10個core線程都處於runnable,且所有線程都在
sendEmail
查看sendEmail代碼
雖然future.get有超時,但是這隻能保證業務線程不阻塞。
future.get並不能打斷線程池的線程。
查看submitCall
雖然future.get有超時,但是這隻能保證業務線程不阻塞。
future.get並不能打斷線程池的線程。
這裏的sendVcWorkerThreadPool#submitCall與ThreadPoolExecutor
ThreadPoolExecutor#submit類似
由於繼承ThreadPoolExecutor,所以調用了ThreadPoolExecutor的execute
ThreadPoolExecutor#execute最終調用了 RunnableFuture#run方法
-
調用鏈
addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run(); -
task即RunnableFuture ,newTaskFor創建了子類FutureTask
因此 查看FutureTask的run方法
-
FutureTask是對Callable的一層封裝。
-
超時隻影響了業務線程(調用futureTask.get的線程),不影響工作線程。
從代碼層面判斷 futureTask.get超時隻影響了業務線程(調用futureTask.get的線程),不影響工作線程。
FutureTask.run
運行完畢,設置結果
此時可以使用future.get出結果
這裏讓【因爲future.get,調用park方法使得等待】的線程 恢復。
future.get
死循環檢測是否完成, 超時後,直接return 當前state
Unsafe.park()本地方法休眠當前線程, HotSpot在Linux中中通過調用pthread_mutex_lock函數把線程交給系統內核進行阻塞。
休眠
檢測的時候,先將當前線程添加到waitNode
測試future.get並不能打斷線程池的線程。
查看工作線程爲何阻塞
雖然已經證明了futureTask.get超時後不會打斷線程池的worker.thread,還是需要查看工作線程爲何阻塞。
再回顧一下堆棧
execute的調用鏈是addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run();
而FutureTask是對Callable的一層封裝。
本身是SendEmailCall本身是一個Callable
我們只需要查看SendEmailCall的call方法爲何一直在運行。
transport.connect
阻塞到readLine
到這裏就很明顯了, 是socket的inputStream調用read的時候阻塞。
socket是可以設置timeout的。
查找timeout的設置
getSocket
to爲read超時時間,
cto爲連接超時時間
如果不設置則都爲永久
而創建的時候,沒有設置timeout
到此就明白了, 設置timeout即可。
修復
props.put("mail.smtp.connectiontimeout", "3000");
props.put("mail.smtp.timeout", "3000");
debug測試
修改後
修改爲超短時間 會報錯。
相關資料
線程池中的線程何時死亡?
SocketInputStream.socketRead0引起線程池提交任務後,futureTask.get超時
socket連接代理socketRead0(Native Method) 線程阻塞處理