Java併發程式設計筆記6:執行緒池的使用

mortal同學發表於2018-12-22

ThreadPoolExecutor

在 Executoors 中的 newChachedThreadPool, newFixedThreadPool, newScheduledThreadExecutor 的工廠方法返回的都是 ThreadPoolExecutor 物件。如果預設的執行策略不能滿足需求,可以通過 ThreadPoolExecutor 的建構函式進行例項化,根據自己的需求定製。

Java併發程式設計筆記6:執行緒池的使用
)

ThreadPoolExecutor(int corePoolSize,//執行緒池基本大小
                   int maximumPoolSize, //執行緒池最大尺寸
                   long keepAliveTime, //執行緒空閒後的存活時間
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, //任務佇列
                   ThreadFactory threadFactory, //執行緒工廠  
                   RejectedExecutionHandler handler) //飽和策略
複製程式碼

執行緒建立和銷燬

  • corePoolSize是執行緒池的基本大小,即在沒有任務執行時執行緒池的大小。
    • 在建立了執行緒池後,預設情況下,執行緒池中並沒有任何執行緒,而是等待有任務到來才建立執行緒去執行任務。
    • 可以呼叫prestartAllCoreThreads()或者prestartCoreThread()方法預建立執行緒,即在沒有任務到來之前就建立corePoolSize個執行緒或者一個執行緒。
    • 預設情況下,在建立了執行緒池後,執行緒池中的執行緒數為0,當有任務來之後,就會建立一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取佇列當中
    • 在工作佇列滿了的情況下才會建立超出這個數量的執行緒。
  • maximumPoolSize是執行緒池的最大尺寸,表示可以同時活動的執行緒數量的上限。
  • keepAliveTime 和 unit 共同表示了執行緒空閒後的存活時間
    • 預設情況下,只有當執行緒池中的執行緒數大於corePoolSize時,keepAliveTime才會起作用,直到執行緒池中的執行緒數不大於corePoolSize。也就是當執行緒池中的執行緒數大於corePoolSize時,如果一個執行緒空閒的時間達到keepAliveTime,則會終止,直到執行緒池中的執行緒數不超過corePoolSize。
    • 如果呼叫了allowCoreThreadTimeOut(boolean)方法,線上程池中的執行緒數不大於corePoolSize時,keepAliveTime引數也會起作用,直到執行緒池中的執行緒數為0

newFixedThreadPool 工廠方法將執行緒池的基本大小和最大大小都設定為引數中指定的值,而且建立的執行緒池不會超時。

newCachedThreadPool工廠方法將執行緒池的最大大小設定為Integer.MAX_VALUE,將基本大小設定為0,並將超時設定為1分鐘,這種方法建立出來的執行緒池可以被無限擴充套件,並且當需求降低時會自動收縮。

管理佇列任務

使用BlokcingQueue來儲存等待執行的任務。基本的佇列有無界佇列,有界佇列和同步移交。佇列的選擇和執行緒池的其他配置引數有關

newFixedThreadPool 和 newSingleThreadExecutor 在預設情況下使用一個無界的LinkedBlockingQueue。如果所有工作者執行緒都處於忙碌狀態, 那麼任務將在佇列中等待。

有界佇列包括ArrayBlockingQueue以及有界的LinkedBlockingQueue和PriorityBlockingQueue。在使用有界佇列時,佇列的大小需要和執行緒池的大小一起調節。執行緒池小而佇列較大,有助於減少記憶體使用量,降低CPU的使用率,同時還可以減少上下文切換,但是可能會限制吞吐量。

對於非常大或無界的執行緒池,可以通過SynchronousQueue來避免任務排隊,以及直接將任務從生產者移交給工作者執行緒。這個佇列的put方法會阻塞,直到有執行緒準備從佇列裡面take,所以本質上SynchronousQueue並不是Queue,它不儲存任何東西,它只是在移交東西,是一種線上程之間進行移交的機制。要將一個任務放到其中,必須有另一個執行緒正在等待接受這個元素。如果沒有執行緒正在等待,並且執行緒池的當前大小小於最大值,那麼ThreadPoolExecutor將建立一個新的執行緒,否則這個任務獎盃拒絕。在 newCachedThreadPool中採用了SynchronousQueue。

飽和策略

當有界對壘被填滿後,飽和策略開始發揮作用。通過setRejectedExecutionHandler來修改。有以下四種飽和策略。

  • AbortPolicy: 飽和策略,使用這種策略的執行緒池,將在無法繼續接受新任務時,給任務提交方丟擲RejectedExecutionException,讓他們決定要如何處理
  • DiscardPolicy:拋棄策略,直接丟棄掉新來的任務
  • CallerRunsPolicy:呼叫者執行策略,這個策略,顧名思義,將把任務交給呼叫方所在的執行緒去執行
  • Discard-OldestPolicy: 拋棄最舊的策略,拋棄下一個獎盃執行的任務,然後嘗試重新提交新的任務。(如果是優先順序佇列,會拋棄優先順序最高的任務,最好不要一起使用)

執行緒工廠

執行緒池在建立執行緒時,通過執行緒工廠方法來完成。在預設的ThreadFactory介面中只定義了一個方法newThread,每當建立新執行緒時都會呼叫這個方法。可以自己建立一個類實現預設的ThreadFactory介面來定製自己的執行緒工廠。

擴充套件ThreadPoolExecutor

ThreadPoolExecutor是可以擴充套件的,它提供了幾個可以在子類中改寫的方法:beforeExecute, afterExecute, terminated。在執行任務的執行緒中將呼叫beforeExecute和afterExecute方法。無論是正常返回還是丟擲異常,afterExecute都被呼叫。如果beforeExecute丟擲一個RuntimeException,任務將不被執行,afterExecute也不會呼叫。

參考資料

相關文章