java多執行緒 ThreadPoolExecutor 策略及坑
無論是使用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()策略,還是從已經建立的連線中進行 的到請求,不要繼續建立連線。