本篇文章講述Java中的線程池問題勺疼,同樣適用于Android中的線程池使用。本篇文章參考:Java線程池分析,Java中的線程池掠归。以上兩位大神的博客有很多干貨掀亩,強(qiáng)烈推薦關(guān)注學(xué)習(xí)。
一、概述
在我們的開發(fā)中經(jīng)常會(huì)使用到多線程铺峭。例如在Android中墓怀,由于主線程的諸多限制,像網(wǎng)絡(luò)請(qǐng)求等一些耗時(shí)的操作我們必須在子線程中運(yùn)行卫键。我們往往會(huì)通過new Thread來開啟一個(gè)子線程傀履,待子線程操作完成以后通過Handler切換到主線程中運(yùn)行。這么以來我們無法管理我們所創(chuàng)建的子線程莉炉,并且無限制的創(chuàng)建子線程钓账,它們相互之間競(jìng)爭(zhēng),很有可能由于占用過多資源而導(dǎo)致死機(jī)或者OOM絮宁。所以在Java中為我們提供了線程池來管理我們所創(chuàng)建的線程梆暮。
線程池的優(yōu)勢(shì)
①降低系統(tǒng)資源消耗,通過重用已存在的線程绍昂,降低線程創(chuàng)建和銷毀造成的消耗啦粹;
②提高系統(tǒng)響應(yīng)速度,當(dāng)有任務(wù)到達(dá)時(shí)治专,無需等待新線程的創(chuàng)建便能立即執(zhí)行卖陵;
③方便線程并發(fā)數(shù)的管控,線程若是無限制的創(chuàng)建张峰,不僅會(huì)額外消耗大量系統(tǒng)資源泪蔫,更是占用過多資源而阻塞系統(tǒng)或oom等狀況,從而降低系統(tǒng)的穩(wěn)定性喘批。線程池能有效管控線程撩荣,統(tǒng)一分配、調(diào)優(yōu)饶深,提供資源使用率餐曹;
④更強(qiáng)大的功能,線程池提供了定時(shí)敌厘、定期以及可控線程數(shù)等功能的線程池台猴,使用方便簡(jiǎn)單。
二俱两、ThreadPoolExecutor
我們可以通過ThreadPoolExecutor來創(chuàng)建一個(gè)線程池饱狂。
ExecutorService service = new ThreadPoolExecutor(....);
下面我們就來看一下ThreadPoolExecutor中的一個(gè)構(gòu)造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ThreadPoolExecutor參數(shù)含義
- corePoolSize
線程池中的核心線程數(shù)宪彩,默認(rèn)情況下休讳,核心線程一直存活在線程池中,即便他們?cè)诰€程池中處于閑置狀態(tài)尿孔。除非我們將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)為true的時(shí)候俊柔,這時(shí)候處于閑置的核心線程在等待新任務(wù)到來時(shí)會(huì)有超時(shí)策略筹麸,這個(gè)超時(shí)時(shí)間由keepAliveTime來指定。一旦超過所設(shè)置的超時(shí)時(shí)間雏婶,閑置的核心線程就會(huì)被終止物赶。
- maximumPoolSize
線程池中所容納的最大線程數(shù),如果活動(dòng)的線程達(dá)到這個(gè)數(shù)值以后尚骄,后續(xù)的新任務(wù)將會(huì)被阻塞块差。包含核心線程數(shù)+非核心線程數(shù)侵续。
- keepAliveTime
非核心線程閑置時(shí)的超時(shí)時(shí)長(zhǎng)倔丈,對(duì)于非核心線程,閑置時(shí)間超過這個(gè)時(shí)間状蜗,非核心線程就會(huì)被回收需五。只有對(duì)ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)為true的時(shí)候,這個(gè)超時(shí)時(shí)間才會(huì)對(duì)核心線程產(chǎn)生效果轧坎。
- unit
用于指定keepAliveTime參數(shù)的時(shí)間單位宏邮。他是一個(gè)枚舉,可以使用的單位有天(TimeUnit.DAYS)缸血,小時(shí)(TimeUnit.HOURS)蜜氨,分鐘(TimeUnit.MINUTES),毫秒(TimeUnit.MILLISECONDS)捎泻,微秒(TimeUnit.MICROSECONDS, 千分之一毫秒)和毫微秒(TimeUnit.NANOSECONDS, 千分之一微秒);
- workQueue
線程池中保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列飒炎。通過線程池中的execute方法提交的Runable對(duì)象都會(huì)存儲(chǔ)在該隊(duì)列中。我們可以選擇下面幾個(gè)阻塞隊(duì)列笆豁。
ArrayBlockingQueue:基于數(shù)組實(shí)現(xiàn)的有界的阻塞隊(duì)列,該隊(duì)列按照FIFO(先進(jìn)先出)原則對(duì)隊(duì)列中的元素進(jìn)行排序郎汪。
LinkedBlockingQueue:基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列,該隊(duì)列按照FIFO(先進(jìn)先出)原則對(duì)隊(duì)列中的元素進(jìn)行排序闯狱。
SynchronousQueue:內(nèi)部沒有任何容量的阻塞隊(duì)列煞赢。在它內(nèi)部沒有任何的緩存空間。對(duì)于SynchronousQueue中的數(shù)據(jù)元素只有當(dāng)我們?cè)囍∽叩臅r(shí)候才可能存在哄孤。
PriorityBlockingQueue:具有優(yōu)先級(jí)的無限阻塞隊(duì)列照筑。
我們還能夠通過實(shí)現(xiàn)BlockingQueue接口來自定義我們所需要的阻塞隊(duì)列。
- threadFactory
線程工廠瘦陈,為線程池提供新線程的創(chuàng)建凝危。ThreadFactory是一個(gè)接口,里面只有一個(gè)newThread方法双饥。 默認(rèn)為DefaultThreadFactory類媒抠。
- handler
是RejectedExecutionHandler對(duì)象,而RejectedExecutionHandler是一個(gè)接口咏花,里面只有一個(gè)rejectedExecution方法阀趴。當(dāng)任務(wù)隊(duì)列已滿并且線程池中的活動(dòng)線程已經(jīng)達(dá)到所限定的最大值或者是無法成功執(zhí)行任務(wù)苍匆,這時(shí)候ThreadPoolExecutor會(huì)調(diào)用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四個(gè)內(nèi)部類實(shí)現(xiàn)了RejectedExecutionHandler接口浸踩。在線程池中它默認(rèn)是AbortPolicy叔汁,在無法處理新任務(wù)時(shí)拋出RejectedExecutionException異常。
下面是在ThreadPoolExecutor中提供的四個(gè)可選值据块。
CallerRunsPolicy:只用調(diào)用者所在線程來運(yùn)行任務(wù)折剃。
AbortPolicy:直接拋出RejectedExecutionException異常另假。
DiscardPolicy:丟棄掉該任務(wù),不進(jìn)行處理
DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù)怕犁,并執(zhí)行當(dāng)前任務(wù)边篮。
我們也可以通過實(shí)現(xiàn)RejectedExecutionHandler接口來自定義我們自己的handler奏甫。如記錄日志或持久化不能處理的任務(wù)。
ThreadPoolExecutor的使用
ExecutorService service = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
對(duì)于ThreadPoolExecutor有多個(gè)構(gòu)造方法思杯,對(duì)于上面的構(gòu)造方法中的其他參數(shù)都采用默認(rèn)值款筑。可以通過execute和submit兩種方式來向線程池提交一個(gè)任務(wù)奈梳。
execute
當(dāng)我們使用execute來提交任務(wù)時(shí)攘须,由于execute方法沒有返回值,所以說我們也就無法判定任務(wù)是否被線程池執(zhí)行成功于宙。
service.execute(new Runnable() {
public void run() {
System.out.println("execute方式");
}
});
submit
當(dāng)我們使用submit來提交任務(wù)時(shí),它會(huì)返回一個(gè)future,我們就可以通過這個(gè)future來判斷任務(wù)是否執(zhí)行成功捞魁,還可以通過future的get方法來獲取返回值。如果子線程任務(wù)沒有完成谱俭,get方法會(huì)阻塞住直到任務(wù)完成宵蛀,而使用get(long timeout, TimeUnit unit)方法則會(huì)阻塞一段時(shí)間后立即返回县貌,這時(shí)候有可能任務(wù)并沒有執(zhí)行完煤痕。
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("submit方式");
return 2;
}
});
try {
Integer number = future.get();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
線程池關(guān)閉
調(diào)用線程池的shutdown()或shutdownNow()方法來關(guān)閉線程池
shutdown原理:將線程池狀態(tài)設(shè)置成SHUTDOWN狀態(tài)梧宫,然后中斷所有沒有正在執(zhí)行任務(wù)的線程。
shutdownNow原理:將線程池的狀態(tài)設(shè)置成STOP狀態(tài)塘匣,然后中斷所有任務(wù)(包括正在執(zhí)行的)的線程兆解,并返回等待執(zhí)行任務(wù)的列表跑揉。
中斷采用interrupt方法,所以無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止现拒。但調(diào)用上述的兩個(gè)關(guān)閉之一望侈,isShutdown()方法返回值為true,當(dāng)所有任務(wù)都已關(guān)閉侥猬,表示線程池關(guān)閉完成捐韩,則isTerminated()方法返回值為true。當(dāng)需要立刻中斷所有的線程瞧预,不一定需要執(zhí)行完任務(wù)仅政,可直接調(diào)用shutdownNow()方法。
三滩愁、線程池執(zhí)行流程
①如果在線程池中的線程數(shù)量沒有達(dá)到核心的線程數(shù)量辫封,這時(shí)候就回啟動(dòng)一個(gè)核心線程來執(zhí)行任務(wù)玖瘸。
②如果線程池中的線程數(shù)量已經(jīng)超過核心線程數(shù)雅倒,這時(shí)候任務(wù)就會(huì)被插入到任務(wù)隊(duì)列中排隊(duì)等待執(zhí)行弧可。
③由于任務(wù)隊(duì)列已滿,無法將任務(wù)插入到任務(wù)隊(duì)列中裁良。這個(gè)時(shí)候如果線程池中的線程數(shù)量沒有達(dá)到線程池所設(shè)定的最大值校套,那么這時(shí)候就會(huì)立即啟動(dòng)一個(gè)非核心線程來執(zhí)行任務(wù)。
④如果線程池中的數(shù)量達(dá)到了所規(guī)定的最大值侨把,那么就會(huì)拒絕執(zhí)行此任務(wù)妹孙,這時(shí)候就會(huì)調(diào)用RejectedExecutionHandler中的rejectedExecution方法來通知調(diào)用者。
四骇笔、四種線程池類
Java中四種具有不同功能常見的線程池嚣崭。他們都是直接或者間接配置ThreadPoolExecutor來實(shí)現(xiàn)他們各自的功能。這四種線程池分別是newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool和newSingleThreadExecutor芦劣。這四個(gè)線程池可以通過Executors類獲取葱跋。
- newFixedThreadPool
通過Executors中的newFixedThreadPool方法來創(chuàng)建,該線程池是一種線程數(shù)量固定的線程池稍味。
ExecutorService service = Executors.newFixedThreadPool(4);
在這個(gè)線程池中所容納最大的線程數(shù)就是我們?cè)O(shè)置的核心線程數(shù)荠卷。如果線程池的線程處于空閑狀態(tài)的話,它們并不會(huì)被回收掂碱,除非是這個(gè)線程池被關(guān)閉。如果所有的線程都處于活動(dòng)狀態(tài)的話沧卢,新任務(wù)就會(huì)處于等待狀態(tài)醉者,直到有線程空閑出來。
由于newFixedThreadPool只有核心線程立磁,并且這些線程都不會(huì)被回收剥槐,也就是它能夠更快速的響應(yīng)外界請(qǐng)求。從下面的newFixedThreadPool方法的實(shí)現(xiàn)可以看出颅崩,newFixedThreadPool只有核心線程温圆,并且不存在超時(shí)機(jī)制,采用LinkedBlockingQueue,所以對(duì)于任務(wù)隊(duì)列的大小也是沒有限制的锅移。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- newCachedThreadPool
通過Executors中的newCachedThreadPool方法來創(chuàng)建饱搏。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通過s上面的newCachedThreadPool方法在這里我們可以看出它的核心線程數(shù)為0,線程池的最大線程數(shù)Integer.MAX_VALUE备绽。而Integer.MAX_VALUE是一個(gè)很大的數(shù)鬓催,也差不多可以說這個(gè)線程池中的最大線程數(shù)可以任意大。
當(dāng)線程池中的線程都處于活動(dòng)狀態(tài)的時(shí)候倍靡,線程池就會(huì)創(chuàng)建一個(gè)新的線程來處理任務(wù)课舍。該線程池中的線程超時(shí)時(shí)長(zhǎng)為60秒他挎,所以當(dāng)線程處于閑置狀態(tài)超過60秒的時(shí)候便會(huì)被回收办桨。這也就意味著若是整個(gè)線程池的線程都處于閑置狀態(tài)超過60秒以后站辉,在newCachedThreadPool線程池中是不存在任何線程的,所以這時(shí)候它幾乎不占用任何的系統(tǒng)資源狸相。
對(duì)于newCachedThreadPool他的任務(wù)隊(duì)列采用的是SynchronousQueue捐川,上面說到在SynchronousQueue內(nèi)部沒有任何容量的阻塞隊(duì)列。SynchronousQueue內(nèi)部相當(dāng)于一個(gè)空集合瘸右,我們無法將一個(gè)任務(wù)插入到SynchronousQueue中岩齿。所以說在線程池中如果現(xiàn)有線程無法接收任務(wù),將會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。
- newScheduledThreadPool
通過Executors中的newScheduledThreadPool方法來創(chuàng)建龄章。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
它的核心線程數(shù)是固定的乞封,對(duì)于非核心線程幾乎可以說是沒有限制的肃晚,并且當(dāng)非核心線程處于限制狀態(tài)的時(shí)候就會(huì)立即被回收。
創(chuàng)建一個(gè)可定時(shí)執(zhí)行或周期執(zhí)行任務(wù)的線程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延遲三秒執(zhí)行");
}
}, 3, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延遲三秒后每隔2秒執(zhí)行");
}
}, 3, 2, TimeUnit.SECONDS);
輸出結(jié)果:
pool-1-thread-2延遲三秒后每隔2秒執(zhí)行
pool-1-thread-1延遲三秒執(zhí)行
pool-1-thread-1延遲三秒后每隔2秒執(zhí)行
pool-1-thread-2延遲三秒后每隔2秒執(zhí)行
pool-1-thread-2延遲三秒后每隔2秒執(zhí)行
schedule(Runnable command, long delay, TimeUnit unit):延遲一定時(shí)間后執(zhí)行Runnable任務(wù)拧廊;
schedule(Callable callable, long delay, TimeUnit unit):延遲一定時(shí)間后執(zhí)行Callable任務(wù)晋修;
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延遲一定時(shí)間后,以間隔period時(shí)間的頻率周期性地執(zhí)行任務(wù)滤港;
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):與scheduleAtFixedRate()方法很類似,但是不同的是scheduleWithFixedDelay()方法的周期時(shí)間間隔是以上一個(gè)任務(wù)執(zhí)行結(jié)束到下一個(gè)任務(wù)開始執(zhí)行的間隔山叮,而scheduleAtFixedRate()方法的周期時(shí)間間隔是以上一個(gè)任務(wù)開始執(zhí)行到下一個(gè)任務(wù)開始執(zhí)行的間隔添履,也就是這一些任務(wù)系列的觸發(fā)時(shí)間都是可預(yù)知的暮胧。
ScheduledExecutorService功能強(qiáng)大,對(duì)于定時(shí)執(zhí)行的任務(wù)往衷,建議多采用該方法席舍。
- newSingleThreadExecutor
通過Executors中的newSingleThreadExecutor方法來創(chuàng)建,在這個(gè)線程池中只有一個(gè)核心線程汰扭,對(duì)于任務(wù)隊(duì)列沒有大小限制福铅,也就意味著這一個(gè)任務(wù)處于活動(dòng)狀態(tài)時(shí),其他任務(wù)都會(huì)在任務(wù)隊(duì)列中排隊(duì)等候依次執(zhí)行笆包。
newSingleThreadExecutor將所有的外界任務(wù)統(tǒng)一到一個(gè)線程中支持拷沸,所以在這個(gè)任務(wù)執(zhí)行之間我們不需要處理線程同步的問題。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
五、線程池的使用技巧
需要針對(duì)具體情況而具體處理序无,不同的任務(wù)類別應(yīng)采用不同規(guī)模的線程池衡创,任務(wù)類別可劃分為CPU密集型任務(wù)、IO密集型任務(wù)和混合型任務(wù)哟玷。(N代表CPU個(gè)數(shù))
對(duì)于CPU密集型任務(wù):線程池中線程個(gè)數(shù)應(yīng)盡量少,如配置N+1個(gè)線程的線程池喉脖;
對(duì)于IO密集型任務(wù):由于IO操作速度遠(yuǎn)低于CPU速度抑月,那么在運(yùn)行這類任務(wù)時(shí),CPU絕大多數(shù)時(shí)間處于空閑狀態(tài)题诵,那么線程池可以配置盡量多些的線程层皱,以提高CPU利用率,如2*N草冈;
對(duì)于混合型任務(wù):可以拆分為CPU密集型任務(wù)和IO密集型任務(wù)臭家,當(dāng)這兩類任務(wù)執(zhí)行時(shí)間相差無幾時(shí),通過拆分再執(zhí)行的吞吐率高于串行執(zhí)行的吞吐率蹄殃,但若這兩類任務(wù)執(zhí)行時(shí)間有數(shù)據(jù)級(jí)的差距你踩,那么沒有拆分的意義。