前段時間,夜晚突然收到報警,緊急上線排查。由于dba操作不當,大片資料復原,發生鎖表的情況,請求傳回時間過長,使得系統列印出大量的rejectedexecutionexception的異常。定位到代碼片段類似:
java代碼
這裡就要說說threadpoolexecutor和arrayblockingqueue了,衆所周知arrayblockingqueue類是一個阻塞的隊列。當和threadpoolexecutor使用時,threadpoolexecutor會在初始化時開啟corepoolsize(也就是上面代碼中的10)個線程,去消費隊列裡的task。當并發量增大,直到corepoolsize全都在執行task,而隊列也放滿了待處理的task的時候,threadpoolexecutor就會去建立一個新的線程。直至達到線程池中的消費線程達到maximumpoolsize(也就是上面代碼裡的600)。如果這個時候再有task加入,根據預設的飽和政策,将會抛出rejectedexecutionexception異常。
這次就是由于擷取資料庫等待時間超長,導緻task響應時間變慢,繼而線程池中活躍的消費線程堆積到600個線程依然無法應付,才抛出的rejectedexecutionexception。
顯然抛出rejectedexecutionexception不是那麼的友好,我們在這裡可以自定義飽和政策。預設系統飽和政策是抛出異常。
另外當線程故障恢複時,通過日志驚奇的觀察到,隊列中的線程以串行的方式運作了一短時間,不過很快就正常了。于是在本地寫了一個測試。
運作如上的代碼,列印日志如下:
可以看到,最後的10個task是以類似串行的方式在運作。
這裡的原因在于threadpoolexecutor的中斷政策,當runnable中抛出rte時,threadpoolexecutor會将執行目前的runnable的線程dead。由于例子的代碼100%會抛出rte,最終的結果就是threadpoolexecutor中存活的消費線程數變為0。threadpoolexecutor建立線程隻有在初始化,調用excute等幾個主動方法中才會去做,我們任務的送出早在一開始就已經做了,最終導緻的結果就是永遠隻存在一個線程服務這個任務隊列。
那麼要比較優雅的解決這個問題,可以使用futuretask,futuretask裡面會處理運作時異常,不會将其抛出給threadpoolexecutor。