Java之線程池使用與原理全面解析
為什么要使用線程池?
JDK1.5后JUC包添加了線程池相關(guān)接口,在Java誕生之初并沒有線程池這個(gè)概念。
剛開始Java程序都是自行創(chuàng)建線程去處理任務(wù)。隨著應(yīng)用使用的線程越來越多,JDK開發(fā)者們發(fā)現(xiàn)有必要使用一個(gè)統(tǒng)一的類來管理這些線程,從而有效提高線程的執(zhí)行效率,減少創(chuàng)建、銷毀線程的開銷。
大量線程的創(chuàng)建、銷毀是非常消耗資源的。創(chuàng)建線程需要消耗一定的內(nèi)存、CPU資源,大量的線程也會(huì)導(dǎo)致大量的線程上下文切換,上下文切換代碼也是相當(dāng)大的,過多的線程導(dǎo)致頻繁切換更是可能使系統(tǒng)性能急速下降。
另外操作系統(tǒng)對(duì)每個(gè)進(jìn)程能創(chuàng)建的線程數(shù)也是有限制的,不可能無限創(chuàng)建。但是大量任務(wù)也不可能只在主線程處理吧,這樣也太慢了。
比如下面的例子,創(chuàng)建上萬的線程,每個(gè)線程打印一下線程名字,觀察一下任務(wù)管理器,CPU直接用完。
這只是簡(jiǎn)單的任務(wù),要是復(fù)雜長(zhǎng)時(shí)間的任務(wù),整個(gè)操作系統(tǒng)可能都會(huì)受到影響(CPU利用率長(zhǎng)時(shí)間下不來,影響其他程序)
public class BadMultiThreadTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println("ThreadName-" + Thread.currentThread().getName());
};
//過多創(chuàng)建線程導(dǎo)致系統(tǒng)資源消耗嚴(yán)重
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(r);
thread.start();
}
}
}線程池就是為了解決上述問題出現(xiàn)的,解決問題的思路很清晰,就是創(chuàng)建好一定數(shù)量的線程,有任務(wù)來了就用這些線程來執(zhí)行任務(wù),任務(wù)過多把池里面的線程都占用了就放隊(duì)列排隊(duì)等候其他任務(wù)執(zhí)行,一旦有空閑線程就可以從隊(duì)列里面取出任務(wù)并執(zhí)行。
有了可控的線程池,系統(tǒng)就不會(huì)處于一種可能隨時(shí)被大量并發(fā)導(dǎo)致線程大量創(chuàng)建,最終壓跨系統(tǒng)的危險(xiǎn)中。
如何使用線程池?
使用線程池去改造上面的例子:
public class MultiThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
executorService.execute(() -> {
System.out.println("ThreadName-" + Thread.currentThread().getName());
});
}
}
}這次再觀察任務(wù)管理器,這次CPU占用不會(huì)說飆長(zhǎng),而是相對(duì)平穩(wěn)。打印的線程名稱就是在ThreadName-pool-1-thread-1到ThreadName-pool-1-thread-10之間變化,說明這一萬個(gè)任務(wù)都是這10個(gè)線程處理的。
線程池的參數(shù)含義
上面的例子簡(jiǎn)單的使用了Excutors工具類來創(chuàng)建了一個(gè)固定線程數(shù)的線程池。
這個(gè)工具類還可以創(chuàng)建單線程線程池、可緩存線程池、定時(shí)執(zhí)行任務(wù)的線程池等。
這些線程池的創(chuàng)建事實(shí)上都是在創(chuàng)建一個(gè)ThreadPoolExecutor實(shí)例,只是通過傳遞不同參數(shù),實(shí)現(xiàn)不同的線程池效果而已。
如果我們把里面的參數(shù)都搞明白,我們就可以根據(jù)實(shí)際需求去自定義線程池,實(shí)際開發(fā)中,我們都應(yīng)該使用自定義的線程池去處理相關(guān)業(yè)務(wù),這樣能最大提高線程池使用效率,提高系統(tǒng)性能。當(dāng)然,這還得靠我們對(duì)業(yè)務(wù)的理解,從而定義出合理的線程池。
通過下面這句代碼,跟蹤源碼
Executors.newFixedThreadPool(10);
最終調(diào)用的函數(shù)如下,它包含了線程池的所有參數(shù)注釋:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}下面簡(jiǎn)單總結(jié)一下參數(shù)的含義:
| 參數(shù)名稱 | 含義 |
| corePoolSize | 核心線程數(shù) |
| maximumPoolSize | 最大線程數(shù) |
| keepAliveTime+時(shí)間單位 | 空閑線程存活時(shí)間 |
| workQueue | 任務(wù)隊(duì)列 |
| threadFactory | 創(chuàng)建線程的工廠類 |
| handler | 被拒絕任務(wù)處理器 |
線程池處理任務(wù)的流程
看一下下面的流程圖就能清晰知道任務(wù)從被提交再到被線程池中線程執(zhí)行的流程了。

如上流程圖,任務(wù)被提交后,線程池會(huì)檢查核心線程數(shù)是否滿了,如果不是,則會(huì)創(chuàng)建一條核心線程以執(zhí)行該任務(wù)。
如果滿了,則把任務(wù)放到阻塞隊(duì)列中,等待有空閑的核心線程來執(zhí)行任務(wù)。阻塞隊(duì)列如果也滿了,則需要檢查最大核心線程數(shù)是否滿了,如果沒滿,創(chuàng)建非核心線程以執(zhí)行任務(wù),如果滿了,表示線程池再也沒能力去處理該任務(wù)了,則需要拒絕策略做處理。
注意到構(gòu)建線程池的參數(shù)中有keepAliveTime+時(shí)間單位這兩個(gè)參數(shù),這兩個(gè)參數(shù)正是用于回收非核心線程用的,當(dāng)非核心線程沒任務(wù)了,空閑時(shí)間達(dá)到keepAliveTime配置的值后將會(huì)被線程池銷毀以回收一些系統(tǒng)資源。
使用線程池的優(yōu)點(diǎn)
- 根據(jù)上面可以簡(jiǎn)略總結(jié)出使用線程池的幾點(diǎn)優(yōu)點(diǎn):
- 解決線程創(chuàng)建、銷毀等生命周期系統(tǒng)消耗過大問題。因?yàn)榫€程池中的線程一旦創(chuàng)建過,核心線程是會(huì)一直駐留在池中待用的,這樣任務(wù)來了后,直接可以執(zhí)行,消除了創(chuàng)建線程的系統(tǒng)開銷。系統(tǒng)響應(yīng)更及時(shí),使用更絲滑。
- 線程池使得系統(tǒng)可控性更強(qiáng)。它避免了系統(tǒng)資源使用不當(dāng)導(dǎo)致系統(tǒng)崩潰,能更合理的使用CPU和內(nèi)存資源。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java非遞歸實(shí)現(xiàn)之二叉樹的前中后序遍歷詳解
樹的遍歷順序大體分為三種:前序遍歷(先根遍歷、先序遍歷),中序遍歷(中根遍歷),后序遍歷(后根遍歷),本文將給大家詳細(xì)的介紹,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值2021-09-09
解決spring boot hibernate 懶加載的問題
這篇文章主要介紹了解決spring boot hibernate 懶加載的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Java實(shí)現(xiàn)ATM系統(tǒng)超全面步驟解讀建議收藏
這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)簡(jiǎn)單ATM機(jī)功能,文中實(shí)現(xiàn)流程寫的非常清晰全面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
springboot如何查找配置文件路徑的順序和其優(yōu)先級(jí)別
此文是在工作中遇到的關(guān)于springboot配置文件的問題,在網(wǎng)上查閱資料和自己測(cè)試之后記錄的,以便日后查閱。希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
SpringBoot自定義MessageConverter與內(nèi)容協(xié)商管理器contentNegotiationManag
這篇文章主要介紹了SpringBoot自定義MessageConverter與內(nèi)容協(xié)商管理器contentNegotiationManager的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10
MyBatisPlus?大數(shù)據(jù)量查詢慢的問題解決
本文主要介紹了MyBatis?Plus?解決大數(shù)據(jù)量查詢慢問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
SpringBoot使用Atomikos技術(shù)整合多數(shù)據(jù)源的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot使用Atomikos技術(shù)整合多數(shù)據(jù)源的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Spring Boot整合Spring Data Jpa代碼實(shí)例
這篇文章主要介紹了Spring Boot整合Spring Data Jpa代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11

