大家好,我是Excutors
,一個老實的工具類。
有個叫老三的程式設計師在文章 要是以前有人這麼講執行緒池,我早就該明白了!裡挖了一個坑,說要把我介紹給大家認識認識。
我其實挺委屈的,作為一個沒得感情,老實幹活的工具類,我卻上了阿里巴巴的黑名單。他們在一本叫《Java開發手冊》的冊子裡寫道:
作者畫外音:人家為啥給你拉黑,不寫的清清楚楚嘛,你有啥可委屈的。而且你這個傢伙就是表面看起來老實,活是你乾的嗎?幹活的不都是小老弟ThreadPoolExecutor
。來,我一個個給你數。
1. newFixedThreadPool
FixedThreadPool
,是一個固定大小的執行緒池。
看一下它的原始碼實現:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
直接呼叫ThreadPoolExecutor
的構造方法。
核心執行緒數
和最大執行緒數
相同- 使用
LinkedBlockingQueue
作為任務佇列
FixedThreadPool
的execute()
執行示意圖:
整體執行過程:
- 當前執行執行緒少於
corePoolSize
,則建立新執行緒執行任務 - 當前執行執行緒大於
corePoolSize
,將任務加入LinkedBlockingQueue
- 執行緒池中執行緒執行完任務後,會迴圈從
LinkedBlockingQueue
中獲取任務執行
因為使用無界佇列LinkedBlockingQueue
來儲存不能執行的任務,所以不會觸發拒絕服務策略,可能會導致OOM
。
2. newSingleThreadExecutor
SingleThreadExecutor
是使用單個執行緒工作的執行緒池。
實現原始碼如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
直接呼叫ThreadPoolExecutor
的構造方法。
核心執行緒數
和最大執行緒數
都是1- 使用
LinkedBlockingQueue
作為任務佇列
SingleThreadExecutor
的執行流程:
- 當前無執行執行緒,建立一個執行緒來執行任務
- 當前有執行緒執行,將任務加入
LinkedBlockingQueue
- 執行緒執行完任務後,會迴圈從
LinkedBlockingQueue
中獲取任務來執行
這裡用了無界佇列LinkedBlockingQueue
,同樣可能會導致OOM
。
3. newCachedThreadPool
CachedThreadPool
是一個會根據需要建立新執行緒的執行緒池。
實現原始碼:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
直接呼叫ThreadPoolExecutor
的構造方法。
核心執行緒數
為0,最大執行緒數
是非常大的一個數字Integer.MAX_VALUE
- 使用沒有容量的
SynchronousQueue
作為工作佇列 keepAliveTime
設定為60L,空閒執行緒空閒60秒之後就會被終止
CachedThreadPool
的執行流程:
- 如果當前有空閒執行緒,使用空閒執行緒來執行任務
- 如果沒有空閒執行緒,建立一個新執行緒來執行任務
- 新建的執行緒執行完任務後,會執行
poll(keepAliveTime,TimeUnit.NANOSECONDS)
,在SynchronousQueue
裡等待60s
這裡執行緒池的大小沒有限制,可能會無限建立執行緒,導致OOM
。
4. newScheduledThreadPool
ScheduledThreadPool
是一個具備排程功能的執行緒池。
實現原始碼:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
可以看到,這個執行緒池不太一樣,它呼叫的是ScheduledThreadPoolExecutor
的構造方法。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
- 最大執行緒數是
Integer.MAX_VALUE
,無限大 - 使用
DelayedWorkQueue
作為任務佇列
ScheduledThreadPoolExecutor
執行任務的流程:
主要分為兩大部分:
- 呼叫
scheduleAtFixedRate()
/scheduleWithFixedDelay()
方法,會向DelayQueue
新增一個ScheduledFutureTask
。 - 執行緒池的執行緒從
DelayQueue
中獲取ScheduledFutureTask
,然後執行任務。
它同樣可以無限建立執行緒,所以也存在OOM
的風險。
為了實現週期性執行任務,ScheduledThreadPoolExecutor
對ThreadPoolExecutor
進行了一些改造[4]:
-
ScheduledFutureTask
來作為排程任務的實現它主要包含了3個成員變數
time(任務將要被執行的具體時間)
、sequenceNumber(任務的序號)
、period(任務執行的間隔週期)
-
使用
DelayQueue
作為任務佇列DelayQueue
封裝了了一個PriorityQueue
,會對對佇列中的ScheduledFutureTask
進行排序,排序的優先順序time>sequenceNumber。
ScheduledThreadPoolExecutor
的任務執行主要分為4步:
- 執行緒池裡的
執行緒1
從DelayQueue
中獲取已到期的ScheduledFutureTask
(DelayQueue.take()) 執行緒1
執行這個ScheduledFutureTask
執行緒1
修改ScheduledFutureTask
的time
變數為下次將要被執行的時間。執行緒1
把這個修改time
之後的ScheduledFutureTask
放回DelayQueue
中(DelayQueue.add())
Excutors自述:這,這……工具類出的問題不叫bug。雖然我偷懶不幹活,還可能會OOM,但我還是一個好工具類,嗚嗚……
作者:是啊,其實Excutors有什麼錯呢?它只是一個沒得感情的工具類,有錯的只是不恰當地用它的人。所以,知其然且知其所以然,搞懂原理,靈活應用。我們應該像一個士兵一樣,不只是會扣動扳機,還會拆解保養槍械。
我是三分惡,一個號稱能文能武的全棧開發。
點贊、關注不迷路,我們們下期見!
參考:
[1]. 《Java併發程式設計的藝術》
[2]. 講真 這次絕對讓你輕鬆學習執行緒池
[3]. 小傅哥 《Java面經手冊》
[4]. 《Java併發程式設計之美》
[5]. 阿里巴巴《Java開發手冊》