大家好,我是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開發手冊》
