線程池
Java中的線程池是運(yùn)用場(chǎng)景最多的并發(fā)框架晚顷,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池。在開(kāi)發(fā)過(guò)程中按声,合理地使用線程池能夠帶來(lái)3個(gè)好處燃少。
- 降低資源消耗
通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的資源消耗。 - 提高響應(yīng)速度
當(dāng)任務(wù)到達(dá)時(shí)席里,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行叔磷。 - 提高線程的可管理性
線程是稀缺資源,如果無(wú)限制地創(chuàng)建奖磁,不僅會(huì)消耗系統(tǒng)資源世澜,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一分配署穗、調(diào)優(yōu)和監(jiān)控寥裂。但是,要做到合理利用線程池案疲,必須對(duì)其實(shí)現(xiàn)原理了如指掌封恰。
線程池的實(shí)現(xiàn)原理
- 線程池判斷核心線程池里的線程是否都在執(zhí)行任務(wù)。如果不是褐啡,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)诺舔。如果核心線程池里的線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程备畦。
- 線程池判斷工作隊(duì)列是否已經(jīng)滿低飒。如果工作隊(duì)列沒(méi)有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里懂盐。如果工作隊(duì)列滿了褥赊,則進(jìn)入下個(gè)流程。
- 線程池判斷線程池的線程是否都處于工作狀態(tài)莉恼。如果沒(méi)有拌喉,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果已經(jīng)滿了俐银,則交給飽和策略來(lái)處理這個(gè)任務(wù)尿背。
線程池的使用
創(chuàng)建線程池
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,milliseconds,runnableTaskQueue, handler);
- corePoolSize(線程池的基本大小)
當(dāng)提交一個(gè)任務(wù)到線程池時(shí)捶惜,線程池會(huì)創(chuàng)建一個(gè)線程執(zhí)行任務(wù)田藐,即使其他空閑的基本線程能夠執(zhí)行新任務(wù)也會(huì)創(chuàng)建線程,等到需要執(zhí)行的任務(wù)數(shù)大于線程池基本大小時(shí)就不再創(chuàng)建。如果調(diào)用了線程池的prestartAllCoreThreads()方法汽久,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有基本線程茴晋。 - maximumPoolSize(線程池最大數(shù)量)
線程池允許創(chuàng)建的最大線程數(shù)。如果隊(duì)列滿了回窘,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)诺擅,則線程池會(huì)再創(chuàng)建新的線程執(zhí)行任務(wù)。值得注意的是啡直,如果使用了無(wú)界的任務(wù)隊(duì)列這個(gè)參數(shù)就沒(méi)什么效果烁涌。 - keepAliveTime(線程活動(dòng)保持時(shí)間)
線程池的工作線程空閑后,保持存活的時(shí)間酒觅。所以撮执,如果任務(wù)很多,并且每個(gè)任務(wù)執(zhí)行的時(shí)間比較短舷丹,可以調(diào)大時(shí)間抒钱,提高線程的利用率。 - milliseconds(線程活動(dòng)保持時(shí)間的單位)
- runnableTaskQueue(任務(wù)隊(duì)列)
用于保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列颜凯∧北遥可以選擇以下幾個(gè)阻塞隊(duì)列。
ArrayBlockingQueue
是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列症概,此隊(duì)列按FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序蕾额。
LinkedBlockingQueue
一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO排序元素彼城,吞吐量通常要高于ArrayBlockingQueue诅蝶。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個(gè)隊(duì)列。
SynchronousQueue
一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列募壕。每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作调炬,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue舱馅,靜態(tài)工廠方Executors.newCachedThreadPool使用了這個(gè)隊(duì)列缰泡。
PriorityBlockingQueue
一個(gè)具有優(yōu)先級(jí)的無(wú)限阻塞隊(duì)列。
數(shù)組隊(duì)列和鏈表隊(duì)列異同點(diǎn)
- 隊(duì)列大小有所不同习柠,ArrayBlockingQueue是有界的初始化必須指定大小匀谣,而LinkedBlockingQueue可以是有界的也可以是無(wú)界的(Integer.MAX_VALUE)照棋,對(duì)于后者而言资溃,當(dāng)添加速度大于移除速度時(shí),在無(wú)界的情況下烈炭,可能會(huì)造成內(nèi)存溢出等問(wèn)題溶锭。
- 數(shù)據(jù)存儲(chǔ)容器不同,ArrayBlockingQueue采用的是數(shù)組作為數(shù)據(jù)存儲(chǔ)容器符隙,而LinkedBlockingQueue采用的則是以Node節(jié)點(diǎn)作為連接對(duì)象的鏈表趴捅。
- 由于ArrayBlockingQueue采用的是數(shù)組的存儲(chǔ)容器垫毙,因此在插入或刪除元素時(shí)不會(huì)產(chǎn)生或銷毀任何額外的對(duì)象實(shí)例,而LinkedBlockingQueue則會(huì)生成一個(gè)額外的Node對(duì)象拱绑。這可能在長(zhǎng)時(shí)間內(nèi)需要高效并發(fā)地處理大批量數(shù)據(jù)的時(shí),對(duì)于GC可能存在較大影響猎拨。
- 兩者的實(shí)現(xiàn)隊(duì)列添加或移除的鎖不一樣膀藐,ArrayBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是沒(méi)有分離的额各,即添加操作和移除操作采用的同一個(gè)ReenterLock鎖,而LinkedBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是分離的吧恃,其添加采用的是putLock,移除采用的則是takeLock痕寓,這樣能大大提高隊(duì)列的吞吐量,也意味著在高并發(fā)的情況下生產(chǎn)者和消費(fèi)者可以并行地操作隊(duì)列中的數(shù)據(jù)呻率,以此來(lái)提高整個(gè)隊(duì)列的并發(fā)性能。
handler(RejectedExecutionHandler)(飽和策略)
當(dāng)隊(duì)列和線程池都滿了筷凤,說(shuō)明線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務(wù)藐守。這個(gè)策略默認(rèn)情況下是AbortPolicy,表示無(wú)法處理新任務(wù)時(shí)拋出異常卢厂。在JDK 1.5中Java線程池框架提供了以下4種策略。
- AbortPolicy:直接拋出異常慎恒。
- CallerRunsPolicy:只用調(diào)用者所在線程來(lái)運(yùn)行任務(wù)任内。
- DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)融柬。
- DiscardPolicy:不處理死嗦,丟棄掉。
提交任務(wù)
- execute()
用于提交不需要返回值的任務(wù)粒氧,所以無(wú)法判斷任務(wù)是否被線程池執(zhí)行成功越除。
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
- submit()
用于提交需要返回值的任務(wù)。線程池會(huì)返回一個(gè)future類型的對(duì)象,通過(guò)這個(gè)future對(duì)象可以判斷任務(wù)是否執(zhí)行成功摘盆,并且可以通過(guò)future的get()方法來(lái)獲取返回值翼雀,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成,而使用get(long timeout孩擂,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回狼渊,這時(shí)候有可能任務(wù)沒(méi)有執(zhí)行完。
Future<Object> future = threadsPool.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 處理中斷異常
} catch (ExecutionException e) {
// 處理無(wú)法執(zhí)行任務(wù)異常
} finally {
// 關(guān)閉線程池
executor.shutdown();
}
關(guān)閉線程池
可以通過(guò)調(diào)用線程池的shutdown或shutdownNow方法來(lái)關(guān)閉線程池类垦。它們存在一定的區(qū)別囤锉,shutdownNow首先將線程池的狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程护锤,并返回等待執(zhí)行任務(wù)的列表官地,而shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程烙懦。
配置線程池
要想合理地配置線程池驱入,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來(lái)分析氯析。(業(yè)務(wù)驅(qū)動(dòng)技術(shù))
- 任務(wù)的性質(zhì)
CPU密集型任務(wù)亏较、IO密集型任務(wù)和混合型任務(wù)。 - 任務(wù)的優(yōu)先級(jí)
高掩缓、中和低雪情。 - 任務(wù)的執(zhí)行時(shí)間
長(zhǎng)、中和短你辣。 - 任務(wù)的依賴性
是否依賴其他系統(tǒng)資源巡通,如數(shù)據(jù)庫(kù)連接。 - 建議
建議使用有界隊(duì)列舍哄。有界隊(duì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力宴凉,可以根據(jù)需要設(shè)大一點(diǎn)兒,比如幾千表悬。
線程池的監(jiān)控
可以通過(guò)線程池提供的參數(shù)進(jìn)行監(jiān)控弥锄,在監(jiān)控線程池的時(shí)候可以使用以下屬性。
- taskCount
線程池需要執(zhí)行的任務(wù)數(shù)量 - completedTaskCount
線程池在運(yùn)行過(guò)程中已完成的任務(wù)數(shù)量蟆沫,小于或等于taskCount籽暇。 - largestPoolSize
線程池里曾經(jīng)創(chuàng)建過(guò)的最大線程數(shù)量。通過(guò)這個(gè)數(shù)據(jù)可以知道線程池是否曾經(jīng)滿過(guò)饭庞。如該數(shù)值等于線程池的最大大小戒悠,則表示線程池曾經(jīng)滿過(guò)救崔。 - getPoolSize
線程池的線程數(shù)量捏顺。如果線程池不銷毀的話,線程池里的線程不會(huì)自動(dòng)銷毀幅骄,所以這個(gè)大小只增不減拆座。 - getActiveCount
獲取活動(dòng)的線程數(shù)。通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控孕索□锾迹可以通過(guò)繼承線程池來(lái)自定義線程池,重寫(xiě)線程池的beforeExecute肄渗、
afterExecute和terminated方法翎嫡,也可以在任務(wù)執(zhí)行前永乌、執(zhí)行后和線程池關(guān)閉前執(zhí)行一些代碼來(lái)進(jìn)行監(jiān)控。例如硝桩,監(jiān)控任務(wù)的平均執(zhí)行時(shí)間碗脊、最大執(zhí)行時(shí)間和最小執(zhí)行時(shí)間等橄妆。這幾個(gè)方法在線程池里是空方法。