無論是使用jdk的線程池ThreadPoolExecutor 還是spring的線程池ThreadPoolTaskExecutor 都會使用到一個阻塞隊列來進行存儲線程任務。
當線程不夠用時,則將后續的任務暫存到 阻塞隊列中,等待有空閑線程來進行。
當這個阻塞隊列滿了的時候,會出現兩種情況
正在運行的線程數量小於 maximumPoolSize,那么還是要創建線程運行這個任務;
正在運行的線程數量大於或等於 maximumPoolSize,那么線程池會通過一個策略進行對后續的任務進行處理。
四種策略
ThreadPoolExecutor.AbortPolicy() 拋出java.util.concurrent.RejectedExecutionException異常
ThreadPoolExecutor.CallerRunsPolicy() 重試添加當前的任務,他會自動重復調用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy() 拋棄舊的任務
ThreadPoolExecutor.DiscardPolicy() 拋棄當前的任務
其他很容易看出來,最近踩了一個ThreadPoolExecutor.CallerRunsPolicy()的坑。
情景是這樣的:我需要通過線程進行創建長連接,當開啟了15個線程的是,線程池最大即達到了maximumPoolSize的數量的時候,繼續增大請求量,會無緣無故的多出來
一個長連接,由於有負載均衡,導致了很多請求無法從長連接中得到。
經過很長時間的測試,最終發現了是ThreadPoolExecutor.CallerRunsPolicy()策略導致的。
這個測試是當你的線程數達到最大,阻塞隊列也滿了的時候,之后的任務會強制先執行,但是沒有了線程誰來執行呢,這個策略會強制中斷主線程進行執行這個任務。
即是說,當我的量上來,線程池不夠用的時候,中斷了我的主線程,主線程沒有長連接,就建立了一個新的長連接,那邊進行了負載均衡,但是這邊不會在進行主線程了,
導致很多請求接收不到。
假設隊列大小為 10,corePoolSize 為 3,maximumPoolSize 為 6,那么當加入 20 個任務時,執行的順序就是這樣的:首先執行任務 1、2、3,然后任務 4~13 被放入隊列。這時候隊列滿了,任務 14、15、16 會被馬上執行,最終順序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。
踩坑完畢
最終做法:
換成ThreadPoolExecutor.DiscardPolicy()策略,還是從已經建立的連接中進行 的到請求,不要繼續創建連接。