1. 程式人生 > >從阿里Java開發手冊學習執行緒池的正確建立方法

從阿里Java開發手冊學習執行緒池的正確建立方法

前言

最近看阿里的 Java開發手冊,上面有執行緒池的一個建議:

【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,
這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。

結合最近面試的經歷,發現這條建議還是十分有用的,因為自己經常使用Executors提供的工廠方法建立執行緒池,所以忽略了執行緒池內部的實現。
特別是拒絕策略,面試被問到兩次,因為使用Executors建立執行緒池不會傳入這個引數而使用預設值所以我們常常忽略這一引數,還好因為這條建議,自己提前熟悉了一下ThreadPoolExecutor。

ThreadPoolExecutor

先看看如何使用ThreadPoolExecutor建立執行緒池:

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

corePoolSize - 執行緒池核心池的大小。
maximumPoolSize - 執行緒池的最大執行緒數。
keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。
unit - keepAliveTime 的時間單位。
workQueue - 用來儲存等待執行任務的佇列。
threadFactory - 執行緒工廠。
handler - 拒絕策略。

關注點1 執行緒池大小

執行緒池有兩個執行緒數的設定,一個為核心池執行緒數,一個為最大執行緒數。
在建立了執行緒池後,預設情況下,執行緒池中並沒有任何執行緒,等到有任務來才建立執行緒去執行任務,除非呼叫了prestartAllCoreThreads()或者prestartCoreThread()方法
當建立的執行緒數等於 corePoolSize 時,會加入設定的阻塞佇列。當佇列滿時,會建立執行緒執行任務直到執行緒池中的數量等於maximumPoolSize。

關注點2 適當的阻塞佇列

java.lang.IllegalStateException: Queue full
方法 丟擲異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
DelayQueue: 一個使用優先順序佇列實現的無界阻塞佇列。
SynchronousQueue: 一個不儲存元素的阻塞佇列。
LinkedTransferQueue: 一個由連結串列結構組成的無界阻塞佇列。
LinkedBlockingDeque: 一個由連結串列結構組成的雙向阻塞佇列。

關注點3 明確拒絕策略

ThreadPoolExecutor.AbortPolicy: 丟棄任務並丟擲RejectedExecutionException異常。 (預設)
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

說明:Executors 各個方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要問題是執行緒數最大數是 Integer.MAX_VALUE,可能會建立數量非常多的執行緒,甚至 OOM。

Executors

讓我們再看看Executors提供的那幾個工廠方法。

newSingleThreadExecutor

建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。
此執行緒池保證所有任務的執行順序按照任務的提交順序執行。

new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())

newFixedThreadPool

建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。
執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒。

new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

newCachedThreadPool

建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,
那麼就會回收部分空閒(60秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。
此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

作者:JavaNoob

出處:https://www.cnblogs.com/javanoob/p/threadpool.html

前言

最近看阿里的 Java開發手冊,上面有執行緒池的一個建議:

【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,
這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。

結合最近面試的經歷,發現這條建議還是十分有用的,因為自己經常使用Executors提供的工廠方法建立執行緒池,所以忽略了執行緒池內部的實現。
特別是拒絕策略,面試被問到兩次,因為使用Executors建立執行緒池不會傳入這個引數而使用預設值所以我們常常忽略這一引數,還好因為這條建議,自己提前熟悉了一下ThreadPoolExecutor。

ThreadPoolExecutor

先看看如何使用ThreadPoolExecutor建立執行緒池:

    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

corePoolSize - 執行緒池核心池的大小。
maximumPoolSize - 執行緒池的最大執行緒數。
keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。
unit - keepAliveTime 的時間單位。
workQueue - 用來儲存等待執行任務的佇列。
threadFactory - 執行緒工廠。
handler - 拒絕策略。

關注點1 執行緒池大小

執行緒池有兩個執行緒數的設定,一個為核心池執行緒數,一個為最大執行緒數。
在建立了執行緒池後,預設情況下,執行緒池中並沒有任何執行緒,等到有任務來才建立執行緒去執行任務,除非呼叫了prestartAllCoreThreads()或者prestartCoreThread()方法
當建立的執行緒數等於 corePoolSize 時,會加入設定的阻塞佇列。當佇列滿時,會建立執行緒執行任務直到執行緒池中的數量等於maximumPoolSize。

關注點2 適當的阻塞佇列

java.lang.IllegalStateException: Queue full
方法 丟擲異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
DelayQueue: 一個使用優先順序佇列實現的無界阻塞佇列。
SynchronousQueue: 一個不儲存元素的阻塞佇列。
LinkedTransferQueue: 一個由連結串列結構組成的無界阻塞佇列。
LinkedBlockingDeque: 一個由連結串列結構組成的雙向阻塞佇列。

關注點3 明確拒絕策略

ThreadPoolExecutor.AbortPolicy: 丟棄任務並丟擲RejectedExecutionException異常。 (預設)
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

說明:Executors 各個方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要問題是執行緒數最大數是 Integer.MAX_VALUE,可能會建立數量非常多的執行緒,甚至 OOM。

Executors

讓我們再看看Executors提供的那幾個工廠方法。

newSingleThreadExecutor

建立一個單執行緒的執行緒池。這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務。如果這個唯一的執行緒因為異常結束,那麼會有一個新的執行緒來替代它。
此執行緒池保證所有任務的執行順序按照任務的提交順序執行。

new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())

newFixedThreadPool

建立固定大小的執行緒池。每次提交一個任務就建立一個執行緒,直到執行緒達到執行緒池的最大大小。
執行緒池的大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒。

new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

newCachedThreadPool

建立一個可快取的執行緒池。如果執行緒池的大小超過了處理任務所需要的執行緒,
那麼就會回收部分空閒(60秒不執行任務)的執行緒,當任務數增加時,此執行緒池又可以智慧的新增新執行緒來處理任務。
此執行緒池不會對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠建立的最大執行緒大小。

new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());