1 基本概念
- 線程池,就是一個(gè)線程的池子,里面有若干線程,它們的目的就是執(zhí)行提交給線程池的任務(wù),執(zhí)行完一個(gè)任務(wù)后不會退出,而是繼續(xù)等待或執(zhí)行新任務(wù)。
- 線程池主要由兩個(gè)概念組成,一個(gè)是任務(wù)隊(duì)列,另一個(gè)是工作者線程,工作者線程主體就是一個(gè)循環(huán),循環(huán)從隊(duì)列中接受任務(wù)并執(zhí)行,任務(wù)隊(duì)列保存待執(zhí)行的任務(wù)。
- 線程池的概念類似于生活中的一些排隊(duì)場景,比如在火車站排隊(duì)購票、在醫(yī)院排隊(duì)掛號、在銀行排隊(duì)辦理業(yè)務(wù)等,一般都由若干個(gè)窗口提供服務(wù),這些服務(wù)窗口類似于工作者線程,而隊(duì)列的概念是類似排隊(duì)的隊(duì)伍。
2 線程池的優(yōu)點(diǎn)
- 它可以重用線程,避免線程創(chuàng)建的開銷。
- 在任務(wù)過多時(shí),通過排隊(duì)避免創(chuàng)建過多線程,減少系統(tǒng)資源消耗和競爭,確保任務(wù)有序完成。
3 理解線程池
3.1 構(gòu)造方法
ThreadPoolExecutor有多個(gè)構(gòu)造方法,都需要一些參數(shù),主要構(gòu)造方法有:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
3.2 線程池大小
線程池的大小主要與四個(gè)參數(shù)有關(guān):
corePoolSize:表示線程池中的核心線程個(gè)數(shù),但并不是一開始就創(chuàng)建這么多線程,剛創(chuàng)建一個(gè)線程池后,不會預(yù)先創(chuàng)建核心線程,只有當(dāng)有任務(wù)時(shí)才會創(chuàng)建;而且核心線程不會因?yàn)榭臻e而被終止,keepAliveTime參數(shù)不適用于它。
maximumPoolSize:表示線程池中的最多線程數(shù),線程的個(gè)數(shù)會動態(tài)變化,但這是最大值,不管有多少任務(wù),都不會創(chuàng)建比這個(gè)值大的線程個(gè)數(shù)。
keepAliveTime:表示當(dāng)線程池中的線程個(gè)數(shù)大于corePoolSize時(shí),額外空閑線程的存活時(shí)間。也就是說,一個(gè)非核心線程,在空閑等待新任務(wù)時(shí),會有一個(gè)最長等待時(shí)間,即keepAliveTime,如果到了時(shí)間還是沒有新任務(wù),就會被終止。如果該值為0,表示所有線程都不會超時(shí)終止。
unit:是keepAliveTime參數(shù)的時(shí)間單位,參數(shù)為TimeUnit的枚舉,常見的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECOND(秒) 等。
一般情況下,有新任務(wù)到來的時(shí)候,如果當(dāng)前線程個(gè)數(shù)小于corePoolSize,就會直接創(chuàng)建一個(gè)新線程來執(zhí)行該任務(wù),即使其他線程現(xiàn)在也是空閑的,也會創(chuàng)建新線程。
如果當(dāng)前線程個(gè)數(shù)大于等于corePoolSize,那就不會立即創(chuàng)建新線程了,它會先嘗試請求加入隊(duì)列,它是"嘗試"入隊(duì),而不是"阻塞等待"入隊(duì)。
如果隊(duì)列滿了或其他原因不能立即入隊(duì),它就不會入隊(duì),而是檢查線程個(gè)數(shù)是否達(dá)到了maximumPoolSize,如果沒有,就會繼續(xù)創(chuàng)建線程,直到線程數(shù)達(dá)到maximumPoolSize。否則任務(wù)將被拒絕。
3.3 隊(duì)列
ThreadPoolExecutor要求的隊(duì)列類型是阻塞隊(duì)列BlockingQueue,它們都可以用作線程池的隊(duì)列,比如:
- LinkedBlockingQueue:基于鏈表的阻塞隊(duì)列,可以指定最大長度,但默認(rèn)是無界的。如果用的是無界隊(duì)列,創(chuàng)建的線程就不會超過 corePoolSize,到達(dá)corePoolSize后,新的任務(wù)總會排隊(duì),參數(shù)maximumPoolSize也就沒有意義了。
- ArrayBlockingQueue:基于數(shù)組的有界阻塞隊(duì)列,有助于防止資源耗盡。
- PriorityBlockingQueue:基于堆的無界阻塞優(yōu)先級隊(duì)列。
- SynchronousQueue:直接提交。沒有實(shí)際存儲空間的同步阻塞隊(duì)列,當(dāng)嘗試排隊(duì)時(shí),只有正好有空閑線程在等待接受任務(wù)時(shí),則其中一個(gè)空閑線程接受該任務(wù);否則總是會創(chuàng)建新線程,直到達(dá)到maximumPoolSize。
3.4 任務(wù)拒絕策略
如果隊(duì)列有界,且maximumPoolSize有限,則當(dāng)隊(duì)列排滿,線程個(gè)數(shù)也達(dá)到了maximumPoolSize,這時(shí)新任務(wù)來了,如何處理呢?此時(shí),會觸發(fā)線程池的任務(wù)拒絕策略。需要強(qiáng)調(diào)下,拒絕策略只有在隊(duì)列有界,且maximumPoolSize有限的情況下才會觸發(fā)。
默認(rèn)情況下,提交任務(wù)的方法如execute/submit/invokeAll等會拋出異常,類型為RejectedExecutionException。不過,拒絕策略是可以自定義的,ThreadPoolExecutor實(shí)現(xiàn)了四種處理方式:
- ThreadPoolExecutor.AbortPolicy:這就是默認(rèn)的方式,拋出異常。
- ThreadPoolExecutor.DiscardPolicy:靜默處理,忽略新任務(wù),不拋異常,也不執(zhí)行。
- ThreadPoolExecutor.DiscardOldestPolicy:將等待時(shí)間最長的任務(wù)扔掉,然后自己排隊(duì)。
- ThreadPoolExecutor.CallerRunsPolicy:在任務(wù)提交者線程中執(zhí)行任務(wù),而不是交給線程池中的線程執(zhí)行。
4 線程池的類型及區(qū)別
類Executors提供了一些靜態(tài)工廠方法,可以方便的創(chuàng)建一些預(yù)配置的線程池,主要方法有:
(1) newSingleThreadExecutor:只有一個(gè)核心線程,確保所有任務(wù)都在同一線程中按順序完成。因此適用于需要確保所有任務(wù)被順序執(zhí)行的場合。
public static ExecutorService newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
只使用一個(gè)線程,使用無界隊(duì)列LinkedBlockingQueue,線程創(chuàng)建后不會超時(shí)終止,該線程順序執(zhí)行所有任務(wù)。
(2)newFixedThreadPool:線程固定,且不會被回收,能夠更快的響應(yīng)外界請求。比較適合在系統(tǒng)負(fù)載高下,通過隊(duì)列對新任務(wù)排隊(duì),保證有足夠的資源處理實(shí)際的任務(wù)。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用固定數(shù)目的n個(gè)線程,使用無界隊(duì)列LinkedBlockingQueue,線程創(chuàng)建后不會超時(shí)終止。和newSingleThreadExecutor一樣,由于是無界隊(duì)列,如果排隊(duì)任務(wù)過多,可能會消耗非常大的內(nèi)存。
(3)newCachedThreadPool:核心線程為0,非核心線程數(shù)量相當(dāng)于無限大,任何任務(wù)都會被立即執(zhí)行。比較適合在系統(tǒng)負(fù)載不太高下,執(zhí)行大量的執(zhí)行時(shí)間比較短的任務(wù)。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它的corePoolSize為0,maximumPoolSize為Integer.MAX_VALUE,keepAliveTime是60秒,隊(duì)列為SynchronousQueue。含義是,當(dāng)新任務(wù)到來時(shí),如果正好有空閑線程在等待任務(wù),則其中一個(gè)空閑線程接受該任務(wù),否則就總是創(chuàng)建一個(gè)新線程,創(chuàng)建的總線程個(gè)數(shù)不受限制。對任一空閑線程,如果60秒內(nèi)沒有新任務(wù),就終止。
(4) ScheduledThreadPool:核心線程數(shù)量固定,非核心線程數(shù)量不定的線程池。適合執(zhí)行定時(shí)任務(wù)或者具有周期性的重復(fù)任務(wù)。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS,
MILLISECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPool的核心線程數(shù)量是固定的,由傳入的corePoolSize參數(shù)決定,非核心線程數(shù)量可以無限大。非核心線程閑置回收的超時(shí)時(shí)間為10秒( DEFAULT_KEEPALIVE_MILLIS的值為10L)。
5 阿里Android手冊的強(qiáng)制要求
線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方
式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。Executors 返回的線程池對象的弊端如下:
- FixedThreadPool和SingleThreadPool : 允 許 的 請 求 隊(duì) 列 長 度 為
Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致OOM; - CachedThreadPool和ScheduledThreadPool : 允 許 的 創(chuàng) 建 線 程 數(shù) 量 為
Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致OOM。
//正例
//返回可用處理器的Java虛擬機(jī)的數(shù)量
int NUMBER_OF_CORES =
Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new
LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(
NUMBER_OF_CORES,
NUMBER_OF_CORES*2,
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
taskQueue,
new BackgroundThreadFactory(),
new DefaultRejectedExecutionHandler());
//反例
ExecutorService cachedThreadPool =
Executors.newCachedThreadPool();
6 總結(jié)
ThreadPoolExecutor實(shí)現(xiàn)了生產(chǎn)者/消費(fèi)者模式,工作者線程就是消費(fèi)者,任務(wù)提交者就是生產(chǎn)者,線程池自己維護(hù)任務(wù)隊(duì)列。當(dāng)我們碰到類似生產(chǎn)者/消費(fèi)者問題時(shí),應(yīng)該優(yōu)先考慮直接使用線程池,而非重新發(fā)明輪子,自己管理和維護(hù)消費(fèi)者線程及任務(wù)隊(duì)列。
7 參考鏈接
Android 線程池的類型、區(qū)別以及為何要用線程池
阿里Anddroid開發(fā)手冊