Executors:為什麼阿里不待見我?
大家好,我是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開發手冊》