SpringBoot監(jiān)控所有線程池的四種解決方案及代碼案例
問題分析
1.默認(rèn)監(jiān)控的局限性
@Component
public class ThreadPoolMonitor {
@Autowired
private ThreadPoolTaskExecutor taskExecutor; // 只能監(jiān)控這一個(gè)線程池
public void monitor() {
// 只能監(jiān)控 taskExecutor 這個(gè)特定的bean
System.out.println("活躍線程: " + taskExecutor.getActiveCount());
}
}
解決方案
方案1:手動(dòng)注冊(cè)所有線程池
@Component
public class ThreadPoolMonitor {
private final Map<String, ThreadPoolTaskExecutor> executors = new ConcurrentHashMap<>();
// 手動(dòng)注冊(cè)線程池
public void registerExecutor(String name, ThreadPoolTaskExecutor executor) {
executors.put(name, executor);
}
@Scheduled(fixedRate = 30000)
public void monitorAll() {
executors.forEach((name, executor) -> {
ThreadPoolExecutor pool = executor.getThreadPoolExecutor();
log.info("線程池[{}] - 活躍: {}/{}, 隊(duì)列: {}/{}, 完成: {}",
name,
pool.getActiveCount(),
pool.getPoolSize(),
pool.getQueue().size(),
pool.getQueue().remainingCapacity() + pool.getQueue().size(),
pool.getCompletedTaskCount());
});
}
}
// 在配置中注冊(cè)
@Configuration
public class ExecutorConfig {
@Autowired
private ThreadPoolMonitor monitor;
@Bean("emailExecutor")
public ThreadPoolTaskExecutor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 配置...
executor.initialize();
// 注冊(cè)到監(jiān)控器
monitor.registerExecutor("emailExecutor", executor);
return executor;
}
@Bean("smsExecutor")
public ThreadPoolTaskExecutor smsExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 配置...
executor.initialize();
monitor.registerExecutor("smsExecutor", executor);
return executor;
}
}
方案2:自動(dòng)發(fā)現(xiàn)所有線程池(推薦)
@Component
public class GlobalThreadPoolMonitor {
@Autowired
private ApplicationContext applicationContext;
@Scheduled(fixedRate = 30000)
public void monitorAllThreadPools() {
// 獲取所有 ThreadPoolTaskExecutor 類型的bean
Map<String, ThreadPoolTaskExecutor> executors =
applicationContext.getBeansOfType(ThreadPoolTaskExecutor.class);
// 獲取所有 ThreadPoolExecutor 類型的bean(直接創(chuàng)建的)
Map<String, ThreadPoolExecutor> nativeExecutors =
applicationContext.getBeansOfType(ThreadPoolExecutor.class);
log.info("=== 線程池監(jiān)控報(bào)告 ===");
// 監(jiān)控 Spring 封裝的線程池
executors.forEach((beanName, executor) -> {
if (executor.getThreadPoolExecutor() != null) {
printPoolStats(beanName, executor.getThreadPoolExecutor());
}
});
// 監(jiān)控原生線程池
nativeExecutors.forEach((beanName, executor) -> {
printPoolStats(beanName, executor);
});
}
private void printPoolStats(String name, ThreadPoolExecutor executor) {
log.info("線程池[{}]: 活躍{}/核心{}, 隊(duì)列{}/{}, 完成任務(wù): {}, 拒絕: {}",
name,
executor.getActiveCount(),
executor.getPoolSize(),
executor.getQueue().size(),
executor.getQueue().size() + executor.getQueue().remainingCapacity(),
executor.getCompletedTaskCount(),
executor.getRejectedExecutionHandler().getClass().getSimpleName());
}
}
方案3:監(jiān)控 @Async 使用的線程池
@Component
public class AsyncThreadPoolMonitor {
@Autowired
private ApplicationContext applicationContext;
@Scheduled(fixedRate = 30000)
public void monitorAsyncPools() {
try {
// 通過反射獲取Spring內(nèi)部的線程池
Map<String, Executor> asyncExecutors =
applicationContext.getBeansOfType(Executor.class);
asyncExecutors.forEach((name, executor) -> {
if (executor instanceof ThreadPoolTaskExecutor) {
ThreadPoolExecutor pool = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor();
printAsyncPoolStats(name, pool);
} else if (executor instanceof ThreadPoolExecutor) {
printAsyncPoolStats(name, (ThreadPoolExecutor) executor);
} else if (executor instanceof TaskExecutor) {
log.info("Executor [{}]: 類型 {}", name, executor.getClass().getSimpleName());
}
});
} catch (Exception e) {
log.warn("監(jiān)控異步線程池失敗: {}", e.getMessage());
}
}
private void printAsyncPoolStats(String name, ThreadPoolExecutor pool) {
double usageRate = pool.getMaximumPoolSize() > 0 ?
(double) pool.getActiveCount() / pool.getMaximumPoolSize() * 100 : 0;
log.warn("異步線程池[{}]: 活躍{}/最大{}, 使用率: {:.1f}%, 隊(duì)列: {}/{}",
name,
pool.getActiveCount(),
pool.getMaximumPoolSize(),
usageRate,
pool.getQueue().size(),
pool.getQueue().size() + pool.getQueue().remainingCapacity());
}
}
方案4:集成Micrometer監(jiān)控(生產(chǎn)環(huán)境推薦)
@Component
public class MicrometerThreadPoolMonitor {
private final MeterRegistry meterRegistry;
private final List<ThreadPoolExecutor> monitoredPools = new ArrayList<>();
public MicrometerThreadPoolMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
// 注冊(cè)要監(jiān)控的線程池
public void registerPool(String name, ThreadPoolExecutor pool) {
monitoredPools.add(pool);
// 注冊(cè)指標(biāo)
Gauge.builder("thread.pool.active.count", pool, ThreadPoolExecutor::getActiveCount)
.tag("pool", name)
.description("活躍線程數(shù)")
.register(meterRegistry);
Gauge.builder("thread.pool.queue.size", pool, p -> p.getQueue().size())
.tag("pool", name)
.description("隊(duì)列大小")
.register(meterRegistry);
Gauge.builder("thread.pool.completed.tasks", pool, ThreadPoolExecutor::getCompletedTaskCount)
.tag("pool", name)
.description("完成任務(wù)數(shù)")
.register(meterRegistry);
}
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
// 應(yīng)用啟動(dòng)后自動(dòng)發(fā)現(xiàn)并注冊(cè)所有線程池
ApplicationContext context = event.getApplicationContext();
Map<String, ThreadPoolTaskExecutor> springExecutors =
context.getBeansOfType(ThreadPoolTaskExecutor.class);
Map<String, ThreadPoolExecutor> nativeExecutors =
context.getBeansOfType(ThreadPoolExecutor.class);
springExecutors.forEach((name, executor) -> {
if (executor.getThreadPoolExecutor() != null) {
registerPool("spring-" + name, executor.getThreadPoolExecutor());
}
});
nativeExecutors.forEach((name, executor) -> {
registerPool("native-" + name, executor);
});
log.info("已注冊(cè)監(jiān)控的線程池?cái)?shù)量: {}", monitoredPools.size());
}
}
完整的生產(chǎn)級(jí)監(jiān)控方案
@Configuration
public class ThreadPoolMonitorConfig {
@Bean
@ConditionalOnMissingBean
public GlobalThreadPoolMonitor globalThreadPoolMonitor() {
return new GlobalThreadPoolMonitor();
}
}
@Component
@Slf4j
public class GlobalThreadPoolMonitor {
@Autowired
private ApplicationContext applicationContext;
private final Map<String, ThreadPoolExecutor> allPools = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
discoverAllThreadPools();
}
@Scheduled(fixedRate = 30000)
public void monitorAllPools() {
if (allPools.isEmpty()) {
discoverAllThreadPools();
}
log.info("======= 線程池監(jiān)控報(bào)告 =======");
allPools.forEach(this::logPoolStatus);
log.info("======= 監(jiān)控報(bào)告結(jié)束 =======");
}
private void discoverAllThreadPools() {
// 發(fā)現(xiàn)Spring封裝的線程池
applicationContext.getBeansOfType(ThreadPoolTaskExecutor.class)
.forEach((name, executor) -> {
if (executor.getThreadPoolExecutor() != null) {
allPools.put("Spring-" + name, executor.getThreadPoolExecutor());
}
});
// 發(fā)現(xiàn)原生線程池
applicationContext.getBeansOfType(ThreadPoolExecutor.class)
.forEach((name, executor) -> {
allPools.put("Native-" + name, executor);
});
// 發(fā)現(xiàn)所有Executor(包括@Async使用的)
applicationContext.getBeansOfType(Executor.class)
.forEach((name, executor) -> {
if (executor instanceof ThreadPoolTaskExecutor) {
ThreadPoolExecutor pool = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor();
allPools.putIfAbsent("Executor-" + name, pool);
} else if (executor instanceof ThreadPoolExecutor) {
allPools.putIfAbsent("Executor-" + name, (ThreadPoolExecutor) executor);
}
});
log.info("發(fā)現(xiàn)線程池?cái)?shù)量: {}", allPools.size());
}
private void logPoolStatus(String name, ThreadPoolExecutor pool) {
int activeCount = pool.getActiveCount();
int poolSize = pool.getPoolSize();
int queueSize = pool.getQueue().size();
int queueCapacity = queueSize + pool.getQueue().remainingCapacity();
long completedTasks = pool.getCompletedTaskCount();
String status = (activeCount == 0) ? "空閑" : "忙碌";
double usageRate = pool.getMaximumPoolSize() > 0 ?
(double) activeCount / pool.getMaximumPoolSize() * 100 : 0;
if (usageRate > 80) {
log.warn("?? 線程池[{}]: {} 活躍{}/最大{} (使用率{:.1f}%), 隊(duì)列{}/{}",
name, status, activeCount, pool.getMaximumPoolSize(), usageRate,
queueSize, queueCapacity);
} else {
log.info("線程池[{}]: {} 活躍{}/核心{}, 隊(duì)列{}/{}, 完成: {}",
name, status, activeCount, poolSize, queueSize, queueCapacity, completedTasks);
}
}
// 獲取特定線程池狀態(tài)
public ThreadPoolStats getPoolStats(String poolName) {
ThreadPoolExecutor pool = allPools.get(poolName);
if (pool != null) {
return new ThreadPoolStats(
pool.getActiveCount(),
pool.getPoolSize(),
pool.getQueue().size(),
pool.getQueue().remainingCapacity(),
pool.getCompletedTaskCount()
);
}
return null;
}
// 統(tǒng)計(jì)類
@Data
@AllArgsConstructor
public static class ThreadPoolStats {
private int activeCount;
private int poolSize;
private int queueSize;
private int queueRemainingCapacity;
private long completedTaskCount;
}
}
總結(jié)
- 默認(rèn)情況下,
ThreadPoolMonitor只能監(jiān)控直接注入的特定線程池 - 需要特殊處理才能監(jiān)控所有線程池:
- 自動(dòng)發(fā)現(xiàn)所有
ThreadPoolTaskExecutor和ThreadPoolExecutorbean - 注冊(cè)機(jī)制手動(dòng)管理
- 集成監(jiān)控框架如 Micrometer
- 自動(dòng)發(fā)現(xiàn)所有
- 生產(chǎn)推薦:使用方案2或方案4的自動(dòng)發(fā)現(xiàn)機(jī)制
關(guān)鍵是要在應(yīng)用啟動(dòng)后自動(dòng)發(fā)現(xiàn)所有線程池實(shí)例,而不是依賴單個(gè)注入。
到此這篇關(guān)于SpringBoot監(jiān)控所有線程池的四種解決方案及代碼案例的文章就介紹到這了,更多相關(guān)SpringBoot監(jiān)控所有線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot Validated失效的問題及解決思路
文章主要介紹了Java Bean Validation(JSR 303/JSR 349)和Hibernate Validator的基本用法,包括常用注解的使用、@Valid和@Validated注解的區(qū)別、如何自定義校驗(yàn)注解以及如何在Spring Boot中使用這些校驗(yàn)機(jī)制2026-01-01
阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式
本文主要介紹了阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
springboot 運(yùn)行 jar 包讀取外部配置文件的問題
這篇文章主要介紹了springboot 運(yùn)行 jar 包讀取外部配置文件,本文主要描述linux系統(tǒng)執(zhí)行jar包讀取jar包同級(jí)目錄的外部配置文件,主要分為兩種方法,每種方法通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07
Java實(shí)現(xiàn)的簡(jiǎn)單數(shù)字處理類及用法示例
這篇文章主要介紹了Java實(shí)現(xiàn)的簡(jiǎn)單數(shù)字處理類及用法,涉及java數(shù)字運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
Java集合Map常見問題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)整理了Java集合Map常見問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
SpringBoot自定義Redis實(shí)現(xiàn)緩存序列化詳解
Spring提供了一個(gè)RedisTemplate來進(jìn)行對(duì)Redis的操作,但是RedisTemplate默認(rèn)配置的是使用Java本機(jī)序列化。如果要對(duì)對(duì)象操作,就不是那么的方便。所以本文為大家介紹了另一種SpringBoot結(jié)合Redis實(shí)現(xiàn)序列化的方法,需要的可以參考一下2022-07-07
使用springboot不自動(dòng)初始化數(shù)據(jù)庫(kù)連接池
這篇文章主要介紹了使用springboot不自動(dòng)初始化數(shù)據(jù)庫(kù)連接池,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
一文詳解MyBatis中動(dòng)態(tài)SQL的封裝原理與常用標(biāo)簽實(shí)戰(zhàn)應(yīng)用
這篇文章主要為大家詳細(xì)介紹了MyBatis中動(dòng)態(tài)SQL的核心技術(shù)與及常用標(biāo)簽的應(yīng)用,同時(shí)提供了最佳實(shí)踐和常見問題解決方案,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-04-04

