java中四種執行緒池的區別
本文按:
一. 執行緒池的使用
二. 幾種執行緒池的區別
三. 如何合理配置執行緒池
一.執行緒池的使用
在Java中,通常使用Executors 獲取執行緒池。常用的執行緒池有以下幾種:
(1)CachedThreadPool
(2)FixedThreadPool
(3)ScheduledThreadPool
(4)SingleThreadExecutor
明確概念:阻塞佇列:
阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。這兩個附加的操作是:
(1)在佇列為空時,獲取元素的執行緒會等待佇列變為非空。
(2)當佇列滿時,儲存元素的執行緒會等待佇列可用。
二.幾種執行緒池的區別
(1)
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- 核心執行緒數-0
- 最大執行緒數-Integer.MAX_VALUE
- 一個執行緒如果在 60還沒有被使用的話會被移除執行緒池
- 阻塞佇列使用SynchronousQueue
- 使用中斷的拒絕策略
特點:
1)按需建立新的執行緒,如果沒有可用執行緒則建立新的執行緒,之前用過的執行緒可能會再次被使用;
2)因為空閒執行緒會被移除執行緒池,因此,如果執行緒池長時間不被使用也不會消耗系統資源、
(2)ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//nThread
- 核心執行緒數=最大執行緒數=引數nThread
- 阻塞佇列使用LinkedBlockingQueue,一個共享的無界佇列
特點:
1)在任何情況下最多隻有nThread個執行緒工作,多餘的Task將會被存放到佇列中等待;
2)如果執行緒在執行任務中被終止,終止之前會建立其他的執行緒代替原來的;
3)執行緒將會一直存在線上程池中,直到呼叫shutDown()方法
(3)ScheduledExecutorService scheduledThreadPool =Executors.newScheduledThreadPool(5);//corePoolSize
使用:
void test(){ scheduledThreadPool.schedule(new CallableTask(), 1, TimeUnit.DAYS);//CallableTask每天執行一次 }
- 核心執行緒數:通過引數指定corePoolSize
- 最大執行緒數:Integer.MAX_VALUE
- 超過corePoolSize的執行緒在執行完任務後即終止
- 阻塞佇列使用DelayedWorkQueue
特點:
1)核心執行緒數將會一直存線上程池中,除非設定了allowCoreThreadTimeOut
2)可以設定執行緒的執行時間
(4)ExecutorService singleExecutorService = Executors.newSingleThreadExecutor();
- 執行緒池中最多同時只有一個執行緒活躍
- 同一時刻只有一個任務執行
- 多餘的任務放在LinkedBlockingQueue中
執行緒池的拒絕策略
先假設一個前提:執行緒池有一個任務佇列,用於快取所有待處理的任務,正在處理的任務將從任務佇列中移除。因此在任務佇列長度有限的情況下就會出現新任務的拒絕處理問題,需要有一種策略來處理應該加入任務佇列卻因為佇列已滿無法加入的情況。另外線上程池關閉的時候也需要對任務加入佇列操作進行額外的協調處理。
RejectedExecutionHandler提供了四種方式來處理任務拒絕策略
1、直接丟棄(DiscardPolicy)
2、丟棄佇列中最老的任務(DiscardOldestPolicy)。
3、拋異常(AbortPolicy)
4、將任務分給呼叫執行緒來執行(CallerRunsPolicy)。
4、將任務分給呼叫執行緒來執行(CallerRunsPolicy)。
要想合理的配置執行緒池,就必須首先分析任務特性,可以從以下幾個角度來進行分析:
- 任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
- 任務的優先順序:高,中和低。
- 任務的執行時間:長,中和短。
- 任務的依賴性:是否依賴其他系統資源,如資料庫連線。
任務性質不同的任務可以用不同規模的執行緒池分開處理。CPU密集型任務配置儘可能少的執行緒數量,如配置Ncpu+1個執行緒的執行緒池。IO密集型任務則由於需要等待IO操作,執行緒並不是一直在執行任務,則配置儘可能多的執行緒,如2*Ncpu。混合型的任務,如果可以拆分,則將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高於序列執行的吞吐率,如果這兩個任務執行時間相差太大,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當前裝置的CPU個數。
優先順序不同的任務可以使用優先順序佇列PriorityBlockingQueue來處理。它可以讓優先順序高的任務先得到執行,需要注意的是如果一直有優先順序高的任務提交到佇列裡,那麼優先順序低的任務可能永遠不能執行。
執行時間不同的任務可以交給不同規模的執行緒池來處理,或者也可以使用優先順序佇列,讓執行時間短的任務先執行。
依賴資料庫連線池的任務,因為執行緒提交SQL後需要等待資料庫返回結果,如果等待的時間越長CPU空閒時間就越長,那麼執行緒數應該設定越大,這樣才能更好的利用CPU。
建議使用有界佇列,有界佇列能增加系統的穩定性和預警能力,可以根據需要設大一點,比如幾千。有一次我們組使用的後臺任務執行緒池的佇列和執行緒池全滿了,不斷的丟擲拋棄任務的異常,通過排查發現是資料庫出現了問題,導致執行SQL變得非常緩慢,因為後臺任務執行緒池裡的任務全是需要向資料庫查詢和插入資料的,所以導致執行緒池裡的工作執行緒全部阻塞住,任務積壓線上程池裡。如果當時我們設定成無界佇列,執行緒池的佇列就會越來越多,有可能會撐滿記憶體,導致整個系統不可用,而不只是後臺任務出現問題。當然我們的系統所有的任務是用的單獨的伺服器部署的,而我們使用不同規模的執行緒池跑不同型別的任務,但是出現這樣問題時也會影響到其他任務。