SocketInputStream.socketRead0引起線程池提交任務後,futureTask.get超時

現象

線上發短信、郵箱驗證碼 的時候超時

在這裏插入圖片描述

結論

  1. SocketInputStream.socketRead0導致線程阻塞,阻塞後佔用了線程池的線程。多次阻塞後最終佔用了全部的core線程。新提交的任務只能入隊,沒有線程來處理。
    由於 socket.read佔用了corePoolSize 個 線程池的工作線程worker.thread , 這裏一共有10個,全都阻塞了。

    而execute提交一個runnable的時候, 在達到corePoolSize後, 會將其放入workQueue中。直到workQueue滿。

    新的任務只能入隊(enQueue),不能被消費。

    所以 futureTask.get 一直超時。

  2. 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) 線程阻塞處理

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