Java之線程池使用與原理全面解析
為什么要使用線程池?
JDK1.5后JUC包添加了線程池相關(guān)接口,在Java誕生之初并沒(méi)有線程池這個(gè)概念。
剛開始Java程序都是自行創(chuàng)建線程去處理任務(wù)。隨著應(yīng)用使用的線程越來(lái)越多,JDK開發(fā)者們發(fā)現(xiàn)有必要使用一個(gè)統(tǒng)一的類來(lái)管理這些線程,從而有效提高線程的執(zhí)行效率,減少創(chuàng)建、銷毀線程的開銷。
大量線程的創(chuàng)建、銷毀是非常消耗資源的。創(chuàng)建線程需要消耗一定的內(nèi)存、CPU資源,大量的線程也會(huì)導(dǎo)致大量的線程上下文切換,上下文切換代碼也是相當(dāng)大的,過(guò)多的線程導(dǎo)致頻繁切換更是可能使系統(tǒng)性能急速下降。
另外操作系統(tǒng)對(duì)每個(gè)進(jìn)程能創(chuàng)建的線程數(shù)也是有限制的,不可能無(wú)限創(chuàng)建。但是大量任務(wù)也不可能只在主線程處理吧,這樣也太慢了。
比如下面的例子,創(chuàng)建上萬(wàn)的線程,每個(gè)線程打印一下線程名字,觀察一下任務(wù)管理器,CPU直接用完。
這只是簡(jiǎn)單的任務(wù),要是復(fù)雜長(zhǎng)時(shí)間的任務(wù),整個(gè)操作系統(tǒng)可能都會(huì)受到影響(CPU利用率長(zhǎng)時(shí)間下不來(lái),影響其他程序)
public class BadMultiThreadTest { public static void main(String[] args) { Runnable r = () -> { System.out.println("ThreadName-" + Thread.currentThread().getName()); }; //過(guò)多創(chuàng)建線程導(dǎo)致系統(tǒng)資源消耗嚴(yán)重 for (int i = 0; i < 10000; i++) { Thread thread = new Thread(r); thread.start(); } } }
線程池就是為了解決上述問(wèn)題出現(xiàn)的,解決問(wèn)題的思路很清晰,就是創(chuàng)建好一定數(shù)量的線程,有任務(wù)來(lái)了就用這些線程來(lái)執(zhí)行任務(wù),任務(wù)過(guò)多把池里面的線程都占用了就放隊(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ì)說(shuō)飆長(zhǎng),而是相對(duì)平穩(wěn)。打印的線程名稱就是在ThreadName-pool-1-thread-1到ThreadName-pool-1-thread-10之間變化,說(shuō)明這一萬(wàn)個(gè)任務(wù)都是這10個(gè)線程處理的。
線程池的參數(shù)含義
上面的例子簡(jiǎn)單的使用了Excutors工具類來(lái)創(chuàng)建了一個(gè)固定線程數(shù)的線程池。
這個(gè)工具類還可以創(chuàng)建單線程線程池、可緩存線程池、定時(shí)執(zhí)行任務(wù)的線程池等。
這些線程池的創(chuàng)建事實(shí)上都是在創(chuàng)建一個(gè)ThreadPoolExecutor實(shí)例,只是通過(guò)傳遞不同參數(shù),實(shí)現(xiàn)不同的線程池效果而已。
如果我們把里面的參數(shù)都搞明白,我們就可以根據(jù)實(shí)際需求去自定義線程池,實(shí)際開發(fā)中,我們都應(yīng)該使用自定義的線程池去處理相關(guān)業(yè)務(wù),這樣能最大提高線程池使用效率,提高系統(tǒng)性能。當(dāng)然,這還得靠我們對(duì)業(yè)務(wù)的理解,從而定義出合理的線程池。
通過(guò)下面這句代碼,跟蹤源碼
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ì)列中,等待有空閑的核心線程來(lái)執(zhí)行任務(wù)。阻塞隊(duì)列如果也滿了,則需要檢查最大核心線程數(shù)是否滿了,如果沒(méi)滿,創(chuàng)建非核心線程以執(zhí)行任務(wù),如果滿了,表示線程池再也沒(méi)能力去處理該任務(wù)了,則需要拒絕策略做處理。
注意到構(gòu)建線程池的參數(shù)中有keepAliveTime+時(shí)間單位這兩個(gè)參數(shù),這兩個(gè)參數(shù)正是用于回收非核心線程用的,當(dāng)非核心線程沒(méi)任務(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)消耗過(guò)大問(wèn)題。因?yàn)榫€程池中的線程一旦創(chuàng)建過(guò),核心線程是會(huì)一直駐留在池中待用的,這樣任務(wù)來(lái)了后,直接可以執(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)websocket代碼示例
這篇文章主要介紹了基于java實(shí)現(xiàn)websocket代碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12Spring Boot集成Thymeleaf模板引擎的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot集成Thymeleaf模板引擎的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02SpringMvc MultipartFile實(shí)現(xiàn)圖片文件上傳示例
本篇文章主要介紹了SpringMvc MultipartFile實(shí)現(xiàn)圖片文件上傳示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02java 8 lambda表達(dá)式list操作分組、過(guò)濾、求和、最值、排序、去重代碼詳解
java8的lambda表達(dá)式提供了一些方便list操作的方法,主要涵蓋分組、過(guò)濾、求和、最值、排序、去重,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Spring?Boot如何監(jiān)控SQL運(yùn)行情況?
Druid是Java語(yǔ)言中最好的數(shù)據(jù)庫(kù)連接池,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot如何監(jiān)控SQL運(yùn)行情況的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04基于Freemarker和xml實(shí)現(xiàn)Java導(dǎo)出word
這篇文章主要介紹了基于Freemarker和xml實(shí)現(xiàn)Java導(dǎo)出word,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Spring Boot如何通過(guò)CORS處理跨域問(wèn)題
這篇文章主要介紹了Spring Boot如何通過(guò)CORS處理跨域問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04