最近一直被隊列的消費業務所困擾,先大致說下業務狀況。
模塊A產生數據通過隊列傳遞給模塊B處理,但是數據來自於定時任務,經常是瞬時上萬條或者更多,而且模塊B的消費有限速控制並且能力有限(消費業務使用的線程池),肯定需要時間消化。
那麼帶來的一個問題就是線程池的拒絕策略選哪種?
首先說下線程池的四種拒絕策略:
- AbortPolicy:直接拋出異常。
- CallerRunsPolicy:只用調用者所在線程來運行任務。
- DiscardOldestPolicy:丟棄隊列裏最老的一個任務,並執行當前任務。
- DiscardPolicy:不處理,丟棄掉。
最初直觀感受就是不能丟消息,用2吧,主線程跟着做業務。
久了,發現一個問題,主線程(暫且這麼說吧,準確說是啓動線程池的線程)一旦運行任務,即使線程池裏的線程跑完任務都不會再進任務,餓着呢。直到主線程跑完一次業務,才能繼續消費,分配給線程池任務。
問題很明顯,業務流程比較耗時,主線程被佔住了,線程池的一旦幹完活,啥都幹不了,都等着主線程消費隊列的數據給新任務呢。
忍不了,但是分析其他三個策略,AbortPolicy直接拋異常,拋了能咋樣,還是不知道要幹啥;DiscardOldestPolicy丟棄老任務,丟消息,否了;DiscardPolicy丟棄,肯定否了。
於是查看拒絕策略源碼,發現拒絕策略的這幾個類真是夠簡潔了(只有兩個方法),統一實現RejectedExecutionHandler接口,實現rejectedExecution方法(代碼幾行……),還有自己的構造方法(空的)。
然後分析自己的業務需求,總結:線程池的阻塞隊列滿了後,主線程啥都不幹,就等着阻塞隊列不滿的時候,把任務扔給線程池。
看源碼發現阻塞隊列正好有個remainingCapacity接口,看看字面意思就知道是啥意思了,不過本着咱們猿們嚴謹的態度,繼續深入看接口源碼(這裏不帶着看了,確認是隊列空餘個數)。然後主線程只要不斷獲取空餘個數,是0就繼續獲取,直到不是0爲止。代碼如下:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
while (e.getQueue().remainingCapacity() == 0);
e.execute(r);
}
}
Runnable r :主線程
ThreadPoolExecutor e:線程池
建議先看看其他四種策略的實現。
原以爲這個問題比較複雜,結果只用幾行代碼搞定,不知道有沒有坑……
ps:如果有問題,請指正交流。如果有更好的方案,歡迎指導。