Java通過Executors提供四種線程池,分別為:
newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池的大小超過了處理任務(wù)所需要的線程,那么就會回收部分空閑(60秒不執(zhí)行任務(wù))的線程,當(dāng)任務(wù)數(shù)增加時,此線程池又可以智能的添加新線程來處理任務(wù)。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待。
newScheduledThreadPool 創(chuàng)建一個無限長線程池,支持定時及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
(1). newCachedThreadPool
創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例代碼如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
線程池為無限大,當(dāng)執(zhí)行第二個任務(wù)時第一個任務(wù)已經(jīng)完成,會復(fù)用執(zhí)行第一個任務(wù)的線程,而不用每次新建線程。
(2). newFixedThreadPool
創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待。示例代碼如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
因?yàn)榫€程池大小為3,每個任務(wù)輸出index后sleep 2秒,所以每兩秒打印3個數(shù)字。
定長線程池的大小最好根據(jù)系統(tǒng)資源進(jìn)行設(shè)置。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。
(3) newScheduledThreadPool
創(chuàng)建一個大小無限的線程池,支持定時及周期性任務(wù)執(zhí)行。延遲執(zhí)行示例代碼如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
表示延遲3秒執(zhí)行。
scheduleAtFixedRate:在任務(wù)執(zhí)行時間小于間隔時間的情況下,程序以起始時間為準(zhǔn)則,每隔指定時間執(zhí)行一次,不受任務(wù)執(zhí)行時間影響;當(dāng)執(zhí)行任務(wù)時間大于間隔時間,等待原有任務(wù)執(zhí)行完成,馬上開啟下一個任務(wù)進(jìn)行執(zhí)行。此時,執(zhí)行間隔時間已經(jīng)被打亂。
scheduleWithFixedDelay:無論任務(wù)執(zhí)行時間長短,都是當(dāng)?shù)谝粋€任務(wù)執(zhí)行完成之后,延遲指定時間再開始執(zhí)行第二個任務(wù)。
定期執(zhí)行示例代碼如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
表示延遲1秒后每3秒執(zhí)行一次。
ScheduledExecutorService比Timer更安全,功能更強(qiáng)大,后面會有一篇單獨(dú)進(jìn)行對比。
(4)、newSingleThreadExecutor
創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。示例代碼如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
結(jié)果依次輸出,相當(dāng)于順序執(zhí)行各個任務(wù)。
現(xiàn)行大多數(shù)GUI程序都是單線程的。Android中單線程可用于數(shù)據(jù)庫操作,文件操作,應(yīng)用批量安裝,應(yīng)用批量刪除等不適合并發(fā)但可能IO阻塞性及影響UI線程響應(yīng)的操作。
線程池的作用:
線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量。根據(jù)系統(tǒng)的環(huán)境情況,可以自動或手動設(shè)置線程數(shù)量,達(dá)到運(yùn)行的最佳效果;少了浪費(fèi)了系統(tǒng)資源,多了造成系統(tǒng)擁擠效率不高。用線程池控制線程數(shù)量,其他線程排 隊(duì)等候。一個任務(wù)執(zhí)行完畢,再從隊(duì)列的中取最前面的任務(wù)開始執(zhí)行。若隊(duì)列中沒有等待進(jìn)程,線程池的這一資源處于等待。當(dāng)一個新任務(wù)需要運(yùn)行時,如果線程池 中有等待的工作線程,就可以開始運(yùn)行了;否則進(jìn)入等待隊(duì)列。
為什么要用線程池:
1.減少了創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復(fù)利用,可執(zhí)行多個任務(wù)。
2.可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^多的內(nèi)存,而把服務(wù)器累趴下(每個線程需要大約1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機(jī))。
3.Java里面線程池的頂級接口是Executor,但是嚴(yán)格意義上講Executor并不是一個線程池,而只是一個執(zhí)行線程的工具。真正的線程池接口是ExecutorService。
比較重要的幾個類:
- ExecutorService
真正的線程池接口。
- ScheduledExecutorService
能和Timer/TimerTask類似,解決那些需要任務(wù)重復(fù)執(zhí)行的問題。
- ThreadPoolExecutor
ExecutorService的默認(rèn)實(shí)現(xiàn)。
- ScheduledThreadPoolExecutor
繼承ThreadPoolExecutor的ScheduledExecutorService接口實(shí)現(xiàn),周期性任務(wù)調(diào)度的類實(shí)現(xiàn)。
ThreadPoolExecutor:
-
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
corePoolSize:線程池維護(hù)線程的最少數(shù)量 (core : 核心)
maximumPoolSize:線程池維護(hù)線程的最大數(shù)量
keepAliveTime: 線程池維護(hù)線程所允許的空閑時間
unit: 線程池維護(hù)線程所允許的空閑時間的單位
workQueue: 線程池所使用的緩沖隊(duì)列
handler: 線程池對拒絕任務(wù)的處理策略
一個任務(wù)通過execute(Runnable)方法被添加到線程池,任務(wù)就是一個Runnable類型的對象,任務(wù)的執(zhí)行方法就是Runnable類型對象的run()方法。
當(dāng)一個任務(wù)通過execute(Runnable)方法欲添加到線程池時:
如果線程池中運(yùn)行的線程 小于corePoolSize ,即使線程池中的線程都處于空閑狀態(tài),也要 創(chuàng)建新的線程 來處理被添加的任務(wù)。
如果線程池中運(yùn)行的線程大于等于corePoolSize,但是緩沖隊(duì)列workQueue未滿 ,那么任務(wù)被放入緩沖隊(duì)列 。
如果此時線程池中的數(shù)量大于corePoolSize,緩沖隊(duì)列workQueue滿(即無法將請求加入隊(duì)列 ),并且線程池中的數(shù)量小于maximumPoolSize,建新的線程 來處理被添加的任務(wù)。
如果此時線程池中的數(shù)量大于corePoolSize,緩沖隊(duì)列workQueue滿,并且線程池中的數(shù)量等于maximumPoolSize ,那么通過 handler 所指定的策略來處理此任務(wù)。
當(dāng)線程池中的線程數(shù)量大于 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止 。這樣,線程池可以動態(tài)的調(diào)整池中的線程數(shù)。
也就是:處理任務(wù)的優(yōu)先級為:
corePoolSize、任務(wù)隊(duì)列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務(wù)。
-
一般情況下,隊(duì)列的大小遵循下面的公式:
queSize <= ClientTimeOut(秒) * TPS; 隊(duì)列大小 小于等于 客戶端超時 * 每秒處理的交易數(shù) unit可選的參數(shù)為java.util.concurrent.TimeUnit中的幾個靜態(tài)屬性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue一共有三種
常用的是: java.util.concurrent.ArrayBlockingQueue
直接提交。 工作隊(duì)列的默認(rèn)選項(xiàng)是 SynchronousQueue ,它將任務(wù)直接提交給線程而不保持它們 。在此,如果不存在可用于立即運(yùn)行任務(wù)的線程 ,則試圖把任務(wù)加入隊(duì)列將失敗,因此會構(gòu)造一個新的線程 。此策略可以避免在處理可能具有內(nèi)部依賴性的請求集時出現(xiàn)鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務(wù) 。當(dāng)命令以超過隊(duì)列所能處理的平均數(shù)連續(xù)到達(dá)時,此策略允許無界線程具有增長的可能性。
無界隊(duì)列。 使用無界隊(duì)列(例如,不具有預(yù)定義容量的 LinkedBlockingQueue )將導(dǎo)致在所有 corePoolSize 線程都忙時新任務(wù)在隊(duì)列中等待。這樣,創(chuàng)建的線程就不會超過 corePoolSize 。(因此,maximumPoolSize 的值也就無效了。)當(dāng)每個任務(wù)完全獨(dú)立于其他任務(wù),即任務(wù)執(zhí)行互不影響時,適合于使用無界隊(duì)列;例如,在 Web 頁服務(wù)器中。這種排隊(duì)可用于處理瞬態(tài)突發(fā)請求,當(dāng)命令以超過隊(duì)列所能處理的平均數(shù)連續(xù)到達(dá)時,此策略允許無界線程具有增長的可能性。
有界隊(duì)列。 當(dāng)使用有限的 maximumPoolSizes 時,有界隊(duì)列(如 ArrayBlockingQueue )有助于防止資源耗盡 ,但是可能較難調(diào)整和控制。隊(duì)列大小和最大池大小可能需要相互折衷:使用大型隊(duì)列和小型池可以最大限度地降低 CPU 使用率、操作系統(tǒng)資源和上下文切換開銷,但是可能導(dǎo)致人工降低吞吐量。如果任務(wù)頻繁阻塞(例如,如果它們是 I/O 邊界),則系統(tǒng)可能為超過您許可的更多線程安排時間。使用小型隊(duì)列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調(diào)度開銷,這樣也會降低吞吐量。
使用無界queue可能會耗盡系統(tǒng)資源
使用有界queue可能不能很好的滿足性能,需要調(diào)節(jié)線程數(shù)和queue大小
線程數(shù)自然也有開銷,所以需要根據(jù)不同應(yīng)用進(jìn)行調(diào)節(jié)
handler有四個選擇
ThreadPoolExecutor.AbortPolicy()
//拋出java.util.concurrent.RejectedExecutionException異常ThreadPoolExecutor.CallerRunsPolicy()
//重試添加當(dāng)前的任務(wù),他會自動重復(fù)調(diào)用execute()方法ThreadPoolExecutor.DiscardOldestPolicy()
//拋棄舊的任務(wù)ThreadPoolExecutor.DiscardPolicy()
// 拋棄當(dāng)前的任務(wù)