使用執行緒池執行緒池的目的:
- 降低資源消耗:創建執行緒和銷毀執行緒會占用系統資源
- 提高回應速度:創建執行緒和銷毀執行緒需要占用時間
- 方便集中管理:為了防止濫用多執行緒,有個統一治理的地方
在《阿里巴巴 java 開發手冊》中指出執行緒資源必須通過執行緒池提供,不允許在應用在顯示的創建執行緒;而且執行緒池不允許使用 Executors 創建,要通過 ThreadPoolExecutor 方式,由于 jdk 中 Executor 框架雖然提供了如 newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等創建執行緒池的方法,但是還是不夠靈活,
1 執行緒數設定規則
但是根據業務的不同,任務可以分為 IO 密集型和計算密集型,針對不同的型別我們設定的執行緒數會有不同的規則:
- 針對 IO 密集型:執行緒池的數量盡量要少,約等于 CPU 的核心數,
- 針對計算密集型:執行緒池的執行緒數量要相對較多,約等于 CPU 核心數*2,
2 執行緒池原理
java 執行緒池的實作原理其實很簡單,就是一個執行緒集合 workerSet 和阻塞佇列 workQueue,當向執行緒池提交一個任務的時候,執行緒池會將任務先放到 workQueue 中,workerSet 中的執行緒會不斷的從 workQueue 中獲取執行緒然后執行,當 workQueue 中沒有任務的時候,worker 就會阻塞,直到佇列中有任務了就取出來繼續執行

3 執行緒池流程
當一個任務提交到執行緒池后,大概的執行流程如下:
- 執行緒池首先會判斷當前運行的執行緒的數量是否是小于 corePoolSize,如果是,則創建一個新的作業執行緒來執行任務,如果都在執行任務,則進入到第二步,
- 判斷 BlockingQueue 是否已經滿了,如果沒有滿,就將執行緒放入 BlockingQueue,否則進入到第三步
- 創建新的執行緒直到執行緒數達到 maximumPoolSize,如果創建一個新的作業執行緒將使當前運行的執行緒數量超過 maximumPoolSize,則交給 RejectedExecutionHandler 來處理任務
4 執行緒池引數
- corePoolSize:核心執行緒數,當提交一個任務給執行緒池時,如果當前執行緒池的執行緒數小于 corePoolSize 的話會一直創建新執行緒執行任務,知道執行緒數等于 corePoolSize,如果當前執行緒數為 corePoolSize,繼續提交的任務被保存到阻塞佇列中,等待被執行;如果執行了執行緒池的 prestartAllCoreThreads()方法,執行緒池會提前創建并啟動所有核心執行緒,
- workQueue:用來保存等待被執行的任務的阻塞佇列,在 JDK 中提供了如下阻塞佇列
- ArrayBlockingQueue:基于陣列結構的有界阻塞佇列,按 FIFO 排序任務
- LinkedBlockingQueue:基于鏈表結構的阻塞,按 FIFO 排序任務,吞吐量通常要高,在未指明容量的時候,容量默認為 Integer.MAX_VALUE,
- SynchronousQueue:一個不存元素的阻塞佇列,每個插入操作必須等另一個執行緒呼叫移除操作,否則插入操作一直處于阻塞狀態,吞吐量通常要高于 LinkedBlockingQueue
- PriorityBlockingQueue:具有優先級的無界阻塞佇列
- DelayQueue:類似于 PriorityBlockingQueue,是二叉堆實作的無界優先級阻塞佇列,要求元素都實作 Delayed 介面,通過執行時延從佇列中提取任務,時間沒到取不出來,
- maximumPoolSize:執行緒池中允許的最大執行緒數,如果當前阻塞佇列滿了,向執行緒池繼續提交任務,如果執行緒池當前的執行緒數小于 maximumPoolSize 的值,就會繼續創建執行緒執行任務,當阻塞佇列是無界佇列的話,則 maximumPoolSize 不起作用,因為無法提交至核心執行緒池的執行緒會一直持續放入 workQueue,
- keepAliveTime:非核心執行緒空閑時的存活時間,即當執行緒沒有任務執行時,該執行緒繼續存活的時間,
- unit: keepAliveTime 的單位
- threadFactory:創建執行緒的工廠,通過自定義的執行緒工廠可以給每個新建的執行緒設定一個具有識別度的執行緒名,默認為 DefaultThreadFactory,
- handler:執行緒池和飽和策略,當阻塞佇列滿了,執行緒池中的執行緒數大于 maximumPoolSize 并且沒有空閑的,如果繼續提交任務的話,必須采取一種策略處理這些執行緒無法處理的任務,執行緒池提供了四種策略:
- AbortPolicy:直接拋出例外,默認策略;
- CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;
- DiscardOldentPolicy:丟棄阻塞佇列中最靠前的任務,并執行當前任務;
- DiscardPolicy:直接丟棄任務,
我們也可以根據實際的應用場景實作 RejectedExecutionHandler 介面,自定義飽和策略,如記錄日志或持久化存盤不能處理的任務,
5 Executor 原始碼決議
FixedThreadPool(定長執行緒池)

特點:
- 使用了 LinkedBlockingQueue,并且沒有指定長度
不足:
- 因為默認值為 Integer.MAX_VALUE,可能會耗費很大的記憶體,甚至 OOM
ScheduledThreadPool(定時執行緒池)

特點:
- 使用了 LinkedBlockingQueue,并且沒有指定長度
不足:
- 因為默認值為 Integer.MAX_VALUE,可能會耗費很大的記憶體,甚至 OOM
CacheThreadPool(可快取執行緒池)

特點:
- maximumPoolSize 的最大值為 Integer.MAX_VALUE,因為其核心執行緒池數為 0,所以當執行緒空閑時達到 60s 后都會被回收,極端情況會出現不會持有任何執行緒資源的情況,
不足:
- 可能導致創建的執行緒非常多,甚至 OOM
SingleThreadExecutor(單執行緒執行緒池)

特點:
- 只有一個核心執行緒,如果 i 該執行緒例外結束,則會創建一個新的執行緒任務來繼續執行任務,唯一的執行緒可以保證所提交的任務的順序執行,使用了 LinkedBlockingQueue 無界佇列
不足:
- 由于使用了無界佇列, 所以 SingleThreadPool 永遠不會拒絕, 即飽和策略失效
6 自定義執行緒池
我們看完原始碼后發現雖然提供了四種執行緒池的實作,但是都是有一定的弊端,很多東西不能自由的定義,所以阿里不推薦用 Executor 是有原因的,我們下面來看一下自定義的執行緒池

說明:
- 核心執行緒數:5
- 最大執行緒數:9
- 非核心執行緒空閑存活時間:20s
- 任務佇列:長度為 1 的 ArrayBlockingQueue

說明:
連續執行 10 次任務,每個任務執行時間為 10s
效果:
首先會使用 5 個核心執行緒,然后把第六個任務放到任務佇列,因為佇列的長度為 1,所以后面的任務到達時會判斷是否達到了最大執行緒數,所以第 7~10 個任務會使執行緒池創建最大執行緒數到 9,然后過了 10s 后有空閑執行緒后再執行第六個任務,執行效果如下圖,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/255173.html
標籤:其他
上一篇:騰訊位置服務的優勢是什么?我們應當如何使用平臺創建應用和服務呼叫的 Key?不同型別的 Key 有何區別?
下一篇:虛擬機安裝步驟
