ThreadPoolExecutor使用和思考(中)-keepAliveTime及拒絕策略

OkidoGreen發表於2017-03-27

工作中多處接觸到了ThreadPoolExecutor。趁著現在還算空,學習總結一下。

 

前記:

 

  1. jdk官方文件(javadoc)是學習的最好,最權威的參考。
  2. 文章分上中下。上篇中主要介紹ThreadPoolExecutor接受任務相關的兩方面入參的意義和區別,池大小引數corePoolSize和maximumPoolSize,BlockingQueue選型(SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue);中篇中主要聊聊與keepAliveTime這個引數相關的話題;下片中介紹一下一些比較少用的該類的API,及他的近親:ScheduledThreadPoolExecutor
  3. 如果理解錯誤,請直接指出。

===============================神奇分割線==================================

通過上篇文章,我們可以總結出:ThreadPoolExecutor中額定的“工人”數量由corePoolSize決定,當任務數量超過額定工人數量時,將任務快取在BlockingQueue之中,當發現如果連queue中也放不下時(可能是因為使用有界queue,也可能是使用SynchronousQueue),ThreadPoolExecutor會請求“老闆”再派幾個“工人”過來

接下來發生的事情有兩種情況:
  1. 任務不再過來了 - keepAliveTime
  2. 任務仍然繼續過來 - RejectedExecutionHandler

===============================神奇分割線==================================

keepAliveTime

jdk中的解釋是:當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。

有點拗口,其實這個不難理解,在使用了“池”的應用中,大多都有類似的引數需要配置。比如資料庫連線池,DBCP中的maxIdle,minIdle引數。

什麼意思?接著上面的解釋,後來向老闆派來的工人始終是“借來的”,俗話說“有借就有還”,但這裡的問題就是什麼時候還了,如果借來的工人剛完成一個任務就還回去,後來發現任務還有,那豈不是又要去借?這一來一往,老闆肯定頭也大死了。

合理的策略:既然借了,那就多借一會兒。直到“某一段”時間後,發現再也用不到這些工人時,便可以還回去了。這裡的某一段時間便是keepAliveTime的含義,TimeUnit為keepAliveTime值的度量。

RejectedExecutionHandler

另一種情況便是,即使向老闆借了工人,但是任務還是繼續過來,還是忙不過來,這時整個隊伍只好拒絕接受了。

RejectedExecutionHandler介面提供了對於拒絕任務的處理的自定方法的機會。在ThreadPoolExecutor中已經預設包含了4中策略,因為原始碼非常簡單,這裡直接貼出來。

CallerRunsPolicy執行緒呼叫執行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。

Java程式碼  收藏程式碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.             if (!e.isShutdown()) {  
  3.                 r.run();  
  4.             }  
  5.         }  
 
這個策略顯然不想放棄執行任務。但是由於池中已經沒有任何資源了,那麼就直接使用呼叫該execute的執行緒本身來執行。

AbortPolicy:處理程式遭到拒絕將丟擲執行時 RejectedExecutionException

Java程式碼  收藏程式碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.             throw new RejectedExecutionException();  
  3.         }  
 這種策略直接丟擲異常,丟棄任務。

DiscardPolicy:不能執行的任務將被刪除

Java程式碼  收藏程式碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.         }  
 這種策略和AbortPolicy幾乎一樣,也是丟棄任務,只不過他不丟擲異常。

DiscardOldestPolicy:如果執行程式尚未關閉,則位於工作佇列頭部的任務將被刪除,然後重試執行程式(如果再次失敗,則重複此過程)

Java程式碼  收藏程式碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.             if (!e.isShutdown()) {  
  3.                 e.getQueue().poll();  
  4.                 e.execute(r);  
  5.             }  
  6.         }  
該策略就稍微複雜一些,在pool沒有關閉的前提下首先丟掉快取在佇列中的最早的任務,然後重新嘗試執行該任務。這個策略需要適當小心。
設想:如果其他執行緒都還在執行,那麼新來任務踢掉舊任務,快取在queue中,再來一個任務又會踢掉queue中最老任務。

總結:

keepAliveTime和maximumPoolSize及BlockingQueue的型別均有關係。如果BlockingQueue是無界的,那麼永遠不會觸發maximumPoolSize,自然keepAliveTime也就沒有了意義。

反之,如果核心數較小,有界BlockingQueue數值又較小,同時keepAliveTime又設的很小,如果任務頻繁,那麼系統就會頻繁的申請回收執行緒。

相關文章