線程池
適合單個任務處理時間比較短
需要處理的任務數量很大
創建方式的選擇:
線程池的創建方法有兩種
- 使用Executors線程工具類 ,直接點 newXxxThreadPool (可以new四種)
- 一種是如下所示,手動創建線程池
線程池的構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
關於使用哪一種方法創建,阿里開發手冊中也提到了
第3條規定:線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程
第4條規定:線程池不允許使用Executors創建,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能讓編寫代碼的攻城獅更加明確線程池的運行規則,規避資源耗盡(OOM)的風險
所以最好使用第二種,采用手動創建線程池的方式
有什么區別和利害關系呢?
直接使用Executors工具類的話 可以創建四種線程,分別是
newFixedThreadPool() 線程數量固定的線程池
newCachedThreadPool() 可緩存線程的線程池
newScheduledThreadPool() 執行定時任務的線程池
newSingleThreadExecutor() 單線程線程池
有什么弊端呢?
先看看newFixedThreadPool()的構造函數
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
LinkedBlockingQueue 任務隊列沒有指定容量,說明可以添加大量的任務,如果大量任務堆積,可能會導致OOM
再看看newCachedThreadPool的構造函數
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
最大線程數設置成了Integer.MAX_VALUE,意味着可以創建大量線程,也可能導致OOM
所以綜上所述,要使用手動創建線程池的方式
手動創建線程池:
線程池的構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
構造方法好幾個,下面這個是參數最多的
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 - 拒絕策略。可以選擇指定的,也可以自定義拒絕策略,實現接口即可
參數詳細說明:
corePoolSize:
是線程池中長期保持的線程數量
maximumPoolSize:
線程池能夠處理的最大線程數量,超過就要執行拒絕策略了(默認為報錯)
keepAliveTime:
當線程池比較閑的時候,超過10s鍾的時間可以把最大的線程數中不用的線程減掉,恢復成核心線程數的數量
(java中也有優化機制,當任務比較多的時候,會去新創建一些線程 執行任務;比較空閑的時候,會把新創建 的臨時線程舍棄掉,最終會保持和核心線程數相等的線程數量)
執行流程:
當有任務的時候,先判斷當前是否有空閑線程,有的話則執行
沒有空閑流程的話,看是否可存入workQueue等待隊列
隊列沒有滿的話添加,加入隊列中,排隊等待執行
如果隊列滿了,再看是否超過了 最大的線程數,如果沒超過 直接創建線程執行
如果超過了最大線程數,會執行拒絕策略
拒絕策略:
AbortPolicy: 丟棄任務並拋出RejectedExecutionException異常。 (默認)
DiscardPolicy:也是丟棄任務,但是不拋出異常。
DiscardOldestPolicy:忽略最早的任務(把最早添加任務到隊列的任務忽略掉,然后執行當前的任務)
CallerRunsPolicy:把超出的任務交給當前線程執行
本來是線程池自己執行的,結果處理不過來就交給當前的主線程處理
演示幾種拒絕策略
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, //核心線程數
4, //最大線程數
10, //空閑等待時間
TimeUnit.SECONDS, //空閑等待時間的單位
new LinkedBlockingQueue<>(3), //等待隊列的長度
new ThreadPoolExecutor.AbortPolicy() //拒絕策略,拋異常
);
for (int i = 0; i < 8; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
執行之后發現:
直接拋出異常了,沒有沒有拋出異常,說明可能是某個人物執行的比較快
最大線程數 + 隊列中的數量 就是該線程池能承受的最大任務量 也就是 7
2.DiscardPolicy 是不會報出任何錯的,只做忽略操作
3.DiscardOldestPolicy 執行忽略最早的,也是執行六次
4.CallerRunsPolicy 執行不了的交給主線程處理
main
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
自定義拒絕策略:
構造方法中,原本設置拒絕策略的參數,就不要設置了,在設置拒絕參數的位置
設置自定義的拒絕策略,可以使用匿名內部類 ,new RejectedExecutionHandler
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定義拒絕策略執行了...");
}
}
);
for (int i = 0; i < 8; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
打印結果:
自定義拒絕策略執行了...
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-4
pool-1-thread-2
pool-1-thread-3