SpringBoot配置線程池的實(shí)現(xiàn)示例
一.線程池簡(jiǎn)介
1.1.什么是線程池
線程池是一種利用池化技術(shù)思想來實(shí)現(xiàn)的線程管理技術(shù),主要是為了復(fù)用線程、便利地管理線程和任務(wù)、并將線程的創(chuàng)建和任務(wù)的執(zhí)行解耦開來。我們可以創(chuàng)建線程池來復(fù)用已經(jīng)創(chuàng)建的線程來降低頻繁創(chuàng)建和銷毀線程所帶來的資源消耗。在JAVA中主要是使用ThreadPoolExecutor類來創(chuàng)建線程池,并且JDK中也提供了Executors工廠類來創(chuàng)建線程池(不推薦使用)。
1.2.線程池的優(yōu)點(diǎn)
降低資源消耗,復(fù)用已創(chuàng)建的線程來降低創(chuàng)建和銷毀線程的消耗。提高響應(yīng)速度,任務(wù)到達(dá)時(shí),可以不需要等待線程的創(chuàng)建立即執(zhí)行。提高線程的可管理性,使用線程池能夠統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
1.3.線程池五種狀態(tài)
線程池ThreadPoolExecutor分別有五種狀態(tài)分別為:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED使用一個(gè)AtomicInteger類型的ctl字段來描述線程池地運(yùn)行狀態(tài)和線程數(shù)量,通過ctl的高三位來表示線程池的5種狀態(tài),低29位表示線程池中現(xiàn)有的線程數(shù)量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); /** * 線程池線程數(shù)地bit數(shù) */ private static final int COUNT_BITS = Integer.SIZE - 3; /** * 線程池中最大線程容量 */ private static final int CAPACITY = (1 << COUNT_BITS) - 1; /** * 表示可接受新任務(wù),且可執(zhí)行隊(duì)列中的任務(wù); */ private static final int RUNNING = -1 << COUNT_BITS; /** * 表示不接受新任務(wù),但可執(zhí)行隊(duì)列中的任務(wù); */ private static final int SHUTDOWN = 0 << COUNT_BITS; /** * 表示不接受新任務(wù),且不再執(zhí)行隊(duì)列中的任務(wù),且中斷正在執(zhí)行的任務(wù); */ private static final int STOP = 1 << COUNT_BITS; /** * 所有任務(wù)已經(jīng)中止,且工作線程數(shù)量為0,最后變遷到這個(gè)狀態(tài)的線程將 * 要執(zhí)行terminated()鉤子方法,只會(huì)有一個(gè)線程執(zhí)行這個(gè)方法; */ private static final int TIDYING = 2 << COUNT_BITS; /** * TERMINATED,中止?fàn)顟B(tài),已經(jīng)執(zhí)行完terminated()鉤子方法; */ private static final int TERMINATED = 3 << COUNT_BITS;
線程池狀態(tài)之間的流轉(zhuǎn)圖如下所示
1.4.線程池處理流程
線程池處理流程代碼如下所示
/** * 在將來某個(gè)時(shí)候執(zhí)行給定的任務(wù)。該任務(wù)可以在新線程中執(zhí)行,也可以在現(xiàn)有的池線程中執(zhí)行。如果由于此執(zhí)行 * 器已關(guān)閉或已達(dá)到其容量,任務(wù)無法提交執(zhí)行,則由當(dāng)前RejectedExecutionHandler處理該任務(wù)。 * Params: * 命令–要執(zhí)行的任務(wù) * Throws: * RejectedExecutionException–如果任務(wù)無法接受執(zhí)行,則由RejectedExecutionHandler自行決定 * NullPointerException–如果命令為null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* 分三步走: 1.如果運(yùn)行的線程少于corePoolSize,請(qǐng)嘗試以給定的命令作為第一個(gè)任務(wù)來啟動(dòng)一個(gè)新線程。 對(duì)addWorker的調(diào)用原子地檢查runState和workerCount,因此,通過返回false,可以防止 在不應(yīng)該添加線程的情況下添加線程的錯(cuò)誤警報(bào)。 2.如果一個(gè)任務(wù)可以成功排隊(duì),那么我們?nèi)匀恍枰屑?xì)檢查我們是否應(yīng)該添加一個(gè)線程(因?yàn)樽陨? 次檢查以來已有的線程已經(jīng)失效),或者池是否在進(jìn)入該方法后關(guān)閉。因此,如果有必要,我們重 新檢查狀態(tài),如果停止,則回滾排隊(duì),如果workerCount為0,則啟動(dòng)一個(gè)新線程。 3.如果我們不能對(duì)任務(wù)進(jìn)行排隊(duì),那么我們嘗試添加一個(gè)新線程。如果它失敗了,我們知道線程池 被關(guān)閉或飽和了,所以拒絕了這項(xiàng)任務(wù)。 */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (!isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
線程池處理流程圖如下所示
二.開始配置
ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。ThreadPoolTaskExecutor是對(duì)ThreadPoolExecutor進(jìn)行了封裝處理。
2.1.依賴導(dǎo)入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
2.2.配置application.properties文件
universe.thread.pool.executor.threadNamePrefix=threadPoolTaskExecutor- universe.thread.pool.executor.queueCapacity=100 universe.thread.pool.executor.rejectedExecutionHandler=java.util.concurrent.ThreadPoolExecutor$AbortPolicy universe.thread.pool.executor.keepAliveSeconds=60
2.3.創(chuàng)建PoolExecutorConfig.java
package com.temperature.humidity.system.config.thread; import lombok.Data; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @Data @Log4j2 public class PoolExecutorConfig { /** * 獲取CPU核數(shù) */ private static final int CPU_NUM; /** * 配置線程池的前綴 */ @Value("${universe.thread.pool.executor.threadNamePrefix}") private String threadNamePrefix; /** * 線程池的核心線程數(shù)。在沒有設(shè)置 allowCoreThreadTimeOut 為 true 的情況下, * 核心線程會(huì)在線程池中一直存活,即使處于閑置狀態(tài)。 */ private Integer corePoolSize; /** * 線程池中的任務(wù)隊(duì)列,通過線程池的 execute() 方法提交的 Runnable * 對(duì)象會(huì)存儲(chǔ)在該隊(duì)列中。 */ @Value("${universe.thread.pool.executor.queueCapacity}") private Integer queueCapacity; /** * 線程池所能容納的最大線程數(shù)。當(dāng)活動(dòng)線程(核心線程+非核心線程)達(dá)到這個(gè)數(shù)值后, * 后續(xù)任務(wù)將會(huì)根據(jù) RejectedExecutionHandler 來進(jìn)行拒絕策略處理。 */ private Integer maxPoolSize; /** * 當(dāng)任務(wù)無法被執(zhí)行時(shí)(超過線程最大容量 maximum 并且 workQueue 已經(jīng)被排滿了)的處理策略, * - AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常 * - DiscardPolicy:丟棄任務(wù),但是不拋出異常。 * - DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新提交被拒絕的任務(wù) * - CallerRunsPolicy:由調(diào)用線程(提交任務(wù)的線程)處理該任務(wù) */ @Value("${universe.thread.pool.executor.rejectedExecutionHandler}") private String rejectedExecutionHandler; /** * 非核心線程 閑置時(shí)的超時(shí)時(shí)長(zhǎng)。超過該時(shí)長(zhǎng),非核心線程就會(huì)被回收。若線程池通設(shè)置 * 核心線程也允許 timeOut,即 allowCoreThreadTimeOut 為 true,則該時(shí)長(zhǎng) * 同樣會(huì)作用于核心線程,在超過aliveTime 時(shí),核心線程也會(huì)被回收,AsyncTask * 配置的線程池就是這樣設(shè)置的。 */ @Value("${universe.thread.pool.executor.keepAliveSeconds}") private Integer keepAliveSeconds; public PoolExecutorConfig() { log.info("該臺(tái)服務(wù)器的CPU核心數(shù)為{}", CPU_NUM); this.corePoolSize = CPU_NUM + 1; this.maxPoolSize = 2 * CPU_NUM; } static { CPU_NUM = Runtime.getRuntime().availableProcessors(); } }
2.4.創(chuàng)建OurPoolExecutor.java
package com.temperature.humidity.system.config.thread; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionHandler; /** * @description 線程池配置類 */ @Configuration @Log4j2 public class OurPoolExecutor { @Autowired private PoolExecutorConfig threadPoolExecutorConfig; @Bean public Executor threadPoolTaskExecutor() { String threadNamePrefix = threadPoolExecutorConfig.getThreadNamePrefix(); ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); try { log.info("開始初始化線程池 -->threadPoolTaskExecutor"); taskExecutor.setThreadNamePrefix(threadNamePrefix); taskExecutor.setCorePoolSize(threadPoolExecutorConfig.getCorePoolSize()); taskExecutor.setMaxPoolSize(threadPoolExecutorConfig.getMaxPoolSize()); taskExecutor.setQueueCapacity(threadPoolExecutorConfig.getQueueCapacity()); //通過反射獲取RejectedExecutionHandlerClass 的類模板 Class<?> rejectedExecutionHandlerClass = Class.forName(threadPoolExecutorConfig.getRejectedExecutionHandler()); //獲取RejectedExecutionHandlerClass類的實(shí)例 RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) rejectedExecutionHandlerClass.newInstance(); taskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler); taskExecutor.setKeepAliveSeconds(threadPoolExecutorConfig.getKeepAliveSeconds()); //進(jìn)行加載 taskExecutor.initialize(); log.info("初始化線程池完成:{}核心線程為{}-->", threadNamePrefix, taskExecutor.getCorePoolSize()); return taskExecutor; } catch (Exception e) { e.printStackTrace(); log.error("初始化線程池失敗:{}失敗原因?yàn)?{}", threadNamePrefix, e.getMessage()); return null; } } }
三.測(cè)試
我們配置線程池后啟動(dòng)我們的SpringBoot項(xiàng)目,可以看到我們的控制臺(tái)打印出來了如下圖中圈中的日志,測(cè)試成功。
到此這篇關(guān)于SpringBoot配置線程池的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot 線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot不同版本Redis設(shè)置JedisConnectionFactory詳解
本文章向大家介紹Spring Boot不同版本Redis設(shè)置JedisConnectionFactory,主要內(nèi)容包括1.X 版本、2.X 版本、2.、基本概念、基礎(chǔ)應(yīng)用、原理機(jī)制和需要注意的事項(xiàng)等,并結(jié)合實(shí)例形式分析了其使用技巧,希望通過本文能幫助到大家理解應(yīng)用這部分內(nèi)容2023-09-09Mybatis-plus+通用mapper(tk.mybatis)的使用
本文主要介紹了Mybatis-plus+通用mapper(tk.mybatis)的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧<BR>2024-03-03Mybatis基于注解形式的sql語句生成實(shí)例代碼
這篇文章主要介紹了 Mybatis基于注解形式的sql語句生成實(shí)例代碼,需要的朋友可以參考下2017-09-09Java之使用POI教你玩轉(zhuǎn)Excel導(dǎo)入與導(dǎo)出
這篇文章主要介紹了Java之使用POI教你玩轉(zhuǎn)Excel導(dǎo)入與導(dǎo)出,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10Java?Lombok實(shí)現(xiàn)手機(jī)號(hào)碼校驗(yàn)的示例代碼
手機(jī)號(hào)碼校驗(yàn)通常是系統(tǒng)開發(fā)中最基礎(chǔ)的功能之一,本文主要介紹了Java?Lombok實(shí)現(xiàn)手機(jī)號(hào)碼校驗(yàn)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07