線程池的優點
- 線程頻繁的
創建=>銷毀=>創建
對系統對開銷很大,使用線程池可以避免重復的開銷 - 方便復用,提高相應速度
- 線程的創建於執行完全分開,方便維護,降低耦合
線程池的實現原理
池化技術
一說到線程池自然就會想到池化技術。
其實所謂池化技術,就是把一些能夠復用的東西放到池中,避免重復創建、銷毀的開銷,從而極大提高性能。
常見池化技術的例如:
- 線程池
- 內存池
- 連接池
Java中的實現
官方接口
JDK 1.5 推出了三大API用來創建線程:
Executors.newCachedThreadPool()
:無限線程池(最大21億)Executors.newFixedThreadPool(nThreads)
:固定大小的線程池Executors.newSingleThreadExecutor()
:單個線程的線程池
這三個API的底層其實都是由同一個類實現的:ThreadPoolExecutor
類
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ThreadPoolExecutor
類
七大參數
ThreadPoolExecutor
類主要有以下七個參數:
int corePoolSize
: 核心線程池大小int maximumPoolSize
: 最大核心線程池大小long keepAliveTime
: 線程空閑后的存活時間TimeUnit unit
: 超時單位BlockingQueue<Runnable> workQueue
: 阻塞隊列ThreadFactory threadFactory
: 線程工廠:創建線程的,一般默認RejectedExecutionHandler handle
: 拒絕策略
四種拒絕策略
拒絕策略就是當隊列滿時,線程如何去處理新來的任務。
Java內置了四種拒絕策略:
-
ThreadPoolExecutor.CallerRunsPolicy()
-
ThreadPoolExecutor.AbortPolicy()
-
ThreadPoolExecutor.DiscardPolicy()
-
ThreadPoolExecutor.DiscardOldestPolicy()
CallerRunsPolicy(調用者運行策略)
功能:只要線程池沒有關閉,就由提交任務的當前線程處理。
使用場景:一般在不允許失敗、對性能要求不高、並發量較小的場景下使用。
AbortPolicy(中止策略)
功能:當觸發拒絕策略時,直接拋出拒絕執行的異常
使用場景:ThreadPoolExecutor
中默認的策略就是AbortPolicy
,由於ExecutorService
接口的系列ThreadPoolExecutor
都沒有顯示的設置拒絕策略,所以默認的都是這個。
DiscardPolicy(丟棄策略)
功能:直接丟棄這個任務,不觸發任何動作
使用場景:提交的任務無關緊要,一般用的少。
DiscardOldestPolicy(棄老策略)
功能:彈出隊列頭部的元素,然后嘗試執行,相當於排隊的時候把第一個人打死,然后自己代替
使用場景:發布消息、修改消息類似場景。當老消息還未執行,此時新的消息又來了,這時未執行的消息的版本比現在提交的消息版本要低就可以被丟棄了。
線程池中的狀態
RUNNING
自然是運行狀態,指可以接受任務執行隊列里的任務SHUTDOWN
指調用了shutdown()
方法,不再接受新任務了,但是隊列里的任務得執行完畢。STOP
指調用了shutdownNow()
方法,不再接受新任務,同時拋棄阻塞隊列里的所有任務並中斷所有正在執行任務。TIDYING
所有任務都執行完畢,在調用shutdown()/shutdownNow()
中都會嘗試更新為這個狀態。TERMINATED
終止狀態,當執行terminated()
后會更新為這個狀態。
處理流程
提交一個任務到線程池中,線程池的處理流程如下:
-
判斷線程池里的核心線程是否都在執行任務
- 是:進入下個流程。
- 否:調用/創建一個新的核心線程來執行任務。
-
線程池判斷工作隊列是否已滿
- 是:進入下個流程。
- 否:將新提交的任務存儲在這個工作隊列里。
-
判斷線程池里的所有線程是否都處於工作狀態
- 是:交給拒絕策略來處理這個任務。
- 否:調用/創建一個新的工作線程來執行任務。
具體使用
創建
不過最好不要使用Executors
來創建線程,原因如下(參考自——阿里巴巴Java開發手冊):
FixedThreadPool
和SingleThreadPool
: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOMCachedThreadPool
: 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM
推薦使用ThreadPoolExecutor
類自行創建
// 自定義線程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),//CPU的核心數,適合CPU密集型任務
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
合理配置線程
線程池不是越大越好,要根據任務類型合理進行配置
- IO 密集型任務:盡可能的多配置線程
- CPU 密集型任務:(大量復雜的運算)應當分配較少的線程
執行
有兩個方法可以執行任務execute
和submit
execute
提交沒有返回值,不能判斷是否執行成功。submit
會返回一個Future
對象,通過Future
的get()
方法來獲取返回值。
區別:
- 接收的參數不一樣
execute
提交的方式只能提交一個Runnable的對象submit
有三種
submit
有返回值,而execute
沒有submit
方便Exception
處理execute
是Executor
接口中唯一定義的方法submit
是ExecutorService
(該接口繼承Executor
)中定義的方法
關閉
線程池使用完畢,需要對其進行關閉,有兩種方法
-
shutdown()
:不再繼續接收新的任務,執行完成已有任務后關閉 -
shutdownNow()
:直接關閉,若果有任務嘗試停止