Java之線程池使用與原理全面解析
為什么要使用線程池?
JDK1.5后JUC包添加了線程池相關(guān)接口,在Java誕生之初并沒有線程池這個概念。
剛開始Java程序都是自行創(chuàng)建線程去處理任務(wù)。隨著應(yīng)用使用的線程越來越多,JDK開發(fā)者們發(fā)現(xiàn)有必要使用一個統(tǒng)一的類來管理這些線程,從而有效提高線程的執(zhí)行效率,減少創(chuàng)建、銷毀線程的開銷。
大量線程的創(chuàng)建、銷毀是非常消耗資源的。創(chuàng)建線程需要消耗一定的內(nèi)存、CPU資源,大量的線程也會導(dǎo)致大量的線程上下文切換,上下文切換代碼也是相當(dāng)大的,過多的線程導(dǎo)致頻繁切換更是可能使系統(tǒng)性能急速下降。
另外操作系統(tǒng)對每個進程能創(chuàng)建的線程數(shù)也是有限制的,不可能無限創(chuàng)建。但是大量任務(wù)也不可能只在主線程處理吧,這樣也太慢了。
比如下面的例子,創(chuàng)建上萬的線程,每個線程打印一下線程名字,觀察一下任務(wù)管理器,CPU直接用完。
這只是簡單的任務(wù),要是復(fù)雜長時間的任務(wù),整個操作系統(tǒng)可能都會受到影響(CPU利用率長時間下不來,影響其他程序)
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ù)過多把池里面的線程都占用了就放隊列排隊等候其他任務(wù)執(zhí)行,一旦有空閑線程就可以從隊列里面取出任務(wù)并執(zhí)行。
有了可控的線程池,系統(tǒng)就不會處于一種可能隨時被大量并發(fā)導(dǎo)致線程大量創(chuàng)建,最終壓跨系統(tǒng)的危險中。
如何使用線程池?
使用線程池去改造上面的例子:
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占用不會說飆長,而是相對平穩(wěn)。打印的線程名稱就是在ThreadName-pool-1-thread-1到ThreadName-pool-1-thread-10之間變化,說明這一萬個任務(wù)都是這10個線程處理的。
線程池的參數(shù)含義
上面的例子簡單的使用了Excutors工具類來創(chuàng)建了一個固定線程數(shù)的線程池。
這個工具類還可以創(chuàng)建單線程線程池、可緩存線程池、定時執(zhí)行任務(wù)的線程池等。
這些線程池的創(chuàng)建事實上都是在創(chuàng)建一個ThreadPoolExecutor實例,只是通過傳遞不同參數(shù),實現(xiàn)不同的線程池效果而已。
如果我們把里面的參數(shù)都搞明白,我們就可以根據(jù)實際需求去自定義線程池,實際開發(fā)中,我們都應(yīng)該使用自定義的線程池去處理相關(guān)業(yè)務(wù),這樣能最大提高線程池使用效率,提高系統(tǒng)性能。當(dāng)然,這還得靠我們對業(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é)一下參數(shù)的含義:
參數(shù)名稱 | 含義 |
corePoolSize | 核心線程數(shù) |
maximumPoolSize | 最大線程數(shù) |
keepAliveTime+時間單位 | 空閑線程存活時間 |
workQueue | 任務(wù)隊列 |
threadFactory | 創(chuàng)建線程的工廠類 |
handler | 被拒絕任務(wù)處理器 |
線程池處理任務(wù)的流程
看一下下面的流程圖就能清晰知道任務(wù)從被提交再到被線程池中線程執(zhí)行的流程了。
如上流程圖,任務(wù)被提交后,線程池會檢查核心線程數(shù)是否滿了,如果不是,則會創(chuàng)建一條核心線程以執(zhí)行該任務(wù)。
如果滿了,則把任務(wù)放到阻塞隊列中,等待有空閑的核心線程來執(zhí)行任務(wù)。阻塞隊列如果也滿了,則需要檢查最大核心線程數(shù)是否滿了,如果沒滿,創(chuàng)建非核心線程以執(zhí)行任務(wù),如果滿了,表示線程池再也沒能力去處理該任務(wù)了,則需要拒絕策略做處理。
注意到構(gòu)建線程池的參數(shù)中有keepAliveTime+時間單位這兩個參數(shù),這兩個參數(shù)正是用于回收非核心線程用的,當(dāng)非核心線程沒任務(wù)了,空閑時間達到keepAliveTime配置的值后將會被線程池銷毀以回收一些系統(tǒng)資源。
使用線程池的優(yōu)點
- 根據(jù)上面可以簡略總結(jié)出使用線程池的幾點優(yōu)點:
- 解決線程創(chuàng)建、銷毀等生命周期系統(tǒng)消耗過大問題。因為線程池中的線程一旦創(chuàng)建過,核心線程是會一直駐留在池中待用的,這樣任務(wù)來了后,直接可以執(zhí)行,消除了創(chuàng)建線程的系統(tǒng)開銷。系統(tǒng)響應(yīng)更及時,使用更絲滑。
- 線程池使得系統(tǒng)可控性更強。它避免了系統(tǒng)資源使用不當(dāng)導(dǎo)致系統(tǒng)崩潰,能更合理的使用CPU和內(nèi)存資源。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot集成Thymeleaf模板引擎的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot集成Thymeleaf模板引擎的完整步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02SpringMvc MultipartFile實現(xiàn)圖片文件上傳示例
本篇文章主要介紹了SpringMvc MultipartFile實現(xiàn)圖片文件上傳示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02java 8 lambda表達式list操作分組、過濾、求和、最值、排序、去重代碼詳解
java8的lambda表達式提供了一些方便list操作的方法,主要涵蓋分組、過濾、求和、最值、排序、去重,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01基于Freemarker和xml實現(xiàn)Java導(dǎo)出word
這篇文章主要介紹了基于Freemarker和xml實現(xiàn)Java導(dǎo)出word,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04