1. 程式人生 > 實用技巧 >Java執行緒池使用的注意事項

Java執行緒池使用的注意事項

專案中使用的執行緒池的地方很多,一直以來感覺對它的引數已經掌握的很好了,但是遇到幾次問題之後才發現欠缺的這麼多

遇到的坑

  • 任務提交後長時間沒有執行

任務進入了佇列,執行緒還在執行之前的任務。本質原因是對執行緒和佇列的優先順序認識不深刻,有一種錯覺以為是所有執行緒都忙的時候才進入任務佇列。實際上相反,是佇列滿的時候才會新建執行緒(執行緒數大於core size時)。

  • 執行緒池中執行緒執行任務中無故消失(從日誌可以看出,任務並未完成,也沒有丟擲異常)

一般情況下,程式碼中只會去捕捉RuntimeException,如果丟擲Error則會導致執行緒退出,而異常資訊又沒有拿到。最佳的解決辦法是給執行緒池設定UncaughtExceptionHandler

回顧執行緒池重要的配置

執行緒池引數

  • corePoolSize:核心執行緒數量
  • maximumPoolSize:最大執行緒數量
  • workQueue:等待佇列

任務提交時,判斷的順序為 corePoolSize --> workQueue --> maximumPoolSize。

這個順序一定不要弄錯了

拒絕策略

RejectedExecutionHandler

  • AbortPolicy:直接丟擲異常,這是預設策略
  • CallerRunsPolicy:用呼叫者所在的執行緒來執行任務
  • DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務
  • DiscardPolicy:直接丟棄任務

預設是丟擲異常,除非想得特別清楚,不然輕易不要使用其他3種策略

內建的四種執行緒池

  • SingleThreadExecutor:單執行緒化的Executor
  • FiexedThreadPool:固定數目執行緒的執行緒池(佇列數沒有限制)
  • CachedThreadPool:可快取的執行緒池(執行緒數沒有限制)
  • ScheduledThreadPool:支援定時及週期性的任務執行的執行緒池,多數情況下可用來替代Timer類

這4個執行緒池都可能存在問題,不建議直接使用,建議使用自定義引數的執行緒池

如何優雅地關閉

執行緒池狀態

執行緒池狀態變換圖

上圖來自深入理解Java執行緒池:ThreadPoolExecutor

  • RUNNING:能接受新提交的任務,並且也能處理阻塞佇列中的任務;

  • SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞佇列中已儲存的任務。

  • STOP:不能接受新任務,也不處理佇列中的任務,會中斷正在處理任務的執行緒。線上程池處於RUNNINGSHUTDOWN狀態時,呼叫shutdownNow()方法會使執行緒池進入到該狀態;

  • TIDYING:如果所有的任務都已終止了,workerCount (有效執行緒數) 為0,執行緒池進入該狀態後會呼叫terminated()方法進入TERMINATED狀態。

  • TERMINATED:在terminated()方法執行完後進入該狀態。

優雅關閉方式

// 執行緒池進入SHUTDOWN狀態,停止接受新的任務
executorService.shutdown();
// 等待執行緒池任務完成
executorService.awaitTermination(30, TimeUnit.SECONDS);

在Spring中,如果執行緒池作為其他Bean中的屬性,則需要在Bean的destroy時,關閉執行緒池

@PreDestroy
public void destroy() {
    executorService.shutdown();
    executorService.awaitTermination(30, TimeUnit.SECONDS);
}

注意事項

  • 執行緒池使用FutureTask的時候如果拒絕策略設定為了 DiscardPolicy和DiscardOldestPolicy並且在被拒絕的任務的Future物件上呼叫無參get方法那麼呼叫執行緒會一直被阻塞。

最佳實踐

  • 不使用系統自帶的四個Executors
  • 設定UncaughtExceptionHandler
  • 優雅的關閉執行緒池

參考