一、线程池核心参数与设计原理
1. 七大参数详解
ThreadPoolExecutor是Java线程池的核心实现类,其构造函数包含七大关键参数,每个参数都直接影响线程池的性能与行为:
- corePoolSize(核心线程数) :线程池的基本线程数量,即使这些线程处于空闲状态,也不会被回收(除非设置了allowCoreThreadTimeOut)。核心线程是线程池的“常驻军”,用于处理日常任务。
- maximumPoolSize(最大线程数) :线程池能容纳的最大线程数量。当核心线程都在忙碌,且工作队列已满时,线程池会创建新的非核心线程来处理任务,直到线程数达到maximumPoolSize。
- keepAliveTime(线程存活时间) :非核心线程空闲后的存活时间。当非核心线程空闲时间超过keepAliveTime时,会被回收,以释放系统资源。
- unit(时间单位) :keepAliveTime的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。
- workQueue(工作队列) :用于存储待执行任务的阻塞队列。当核心线程都在忙碌时,新提交的任务会进入此队列等待执行。
- threadFactory(线程工厂) :用于创建线程的工厂类。可以通过自定义线程工厂为线程设置有意义的名称,便于问题排查。
- handler(拒绝策略) :当线程池和工作队列都已满时,新提交的任务会被拒绝,此时会执行拒绝策略。JDK提供了四种默认拒绝策略:AbortPolicy(抛出异常,默认)、CallerRunsPolicy(由提交任务的线程执行)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后重新提交)。
2. 线程池执行流程
线程池的执行流程是其核心设计逻辑的体现,具体流程如下:
流程解析:
- 当提交一个新任务时,线程池首先判断核心线程数是否已满。如果未满,直接创建核心线程执行任务。
- 如果核心线程已满,任务会进入工作队列等待执行。
- 如果工作队列也满了,线程池会判断当前线程数是否达到最大线程数。如果未达到,创建非核心线程执行任务。
- 如果线程数已达最大值,执行拒绝策略处理任务。
3. 队列类型与选择策略
工作队列的选择直接影响线程池的性能与行为,常见的阻塞队列有以下几种:
- ArrayBlockingQueue:基于数组的有界阻塞队列,按照FIFO原则排序元素。其容量固定,能防止任务无限堆积,但需要合理设置容量,避免过早触发拒绝策略。
- LinkedBlockingQueue:基于链表的无界(或有界)阻塞队列,同样按照FIFO原则排序元素。当不设置容量时,默认容量为Integer.MAX_VALUE,此时maximumPoolSize参数无效,因为队列永远不会满,线程池最多只有corePoolSize个线程。
- SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作。使用此队列时,线程池会直接创建新线程处理任务(只要线程数未达maximumPoolSize),适用于任务处理速度快、生产消费平衡的场景。
- PriorityBlockingQueue:支持优先级的无界阻塞队列,任务按照优先级顺序执行。需要注意的是,优先级高的任务可能会导致优先级低的任务长期得不到执行(饥饿问题)。
选择策略:
- 对于任务量稳定、执行时间短的场景,可选择ArrayBlockingQueue,设置合理的容量,结合corePoolSize和maximumPoolSize,平衡性能与资源消耗。
- 对于任务量波动大、执行时间长的场景,可选择LinkedBlockingQueue(设置容量),避免任务丢失,同时通过监控及时调整参数。
- 对于高并发、任务处理速度快的场景,可选择SynchronousQueue,配合较大的maximumPoolSize,提高任务处理效率。
4. 拒绝策略分析
当线程池和工作队列都已满时,拒绝策略是最后的保障,不同策略适用于不同场景:
- AbortPolicy:默认策略,抛出RejectedExecutionException异常。适用于任务不能丢失、需要及时感知失败的场景,调用方需捕获异常并处理。
- CallerRunsPolicy:由提交任务的线程执行任务。此策略能降低任务提交速度,给线程池留出缓冲时间,适用于任务不能丢失、且允许提交线程执行任务的场景。
- DiscardPolicy:直接丢弃任务,不做任何处理。适用于任务不重要、丢失不影响业务的场景。
- DiscardOldestPolicy:丢弃队列中最老的任务(即队列头部的任务),然后重新提交当前任务。适用于任务有优先级、新任务更重要的场景,但需注意可能导致老任务长期被丢弃。
二、生产环境线程池参数动态调优
1. 动态调优的理论基础
线程池的参数设置并非一成不变,生产环境中业务流量可能会发生变化(如促销活动导致流量突增),此时需要动态调整线程池参数,以适应业务变化。ThreadPoolExecutor提供了setCorePoolSize、setMaximumPoolSize、setKeepAliveTime等方法,支持在运行时修改参数。
动态调优的关键在于监控指标的采集与分析,常见的监控指标包括:
- 线程池活跃线程数(activeCount) :当前正在执行任务的线程数。
- 线程池线程总数(poolSize) :当前线程池中的线程总数。
- 工作队列大小(queue.size()) :工作队列中等待执行的任务数。
- 任务执行时间:单个任务的平均执行时间、最大执行时间。
- 任务拒绝次数:被拒绝策略处理的任务次数。
通过监控这些指标,可以判断线程池的负载情况,进而调整参数:
- 当活跃线程数接近corePoolSize,且队列大小持续增长时,说明核心线程数不足,可适当增加corePoolSize。
- 当活跃线程数接近maximumPoolSize,且队列大小已满时,说明最大线程数不足,可适当增加maximumPoolSize。
- 当队列大小长期处于高位,且任务执行时间较长时,说明任务处理逻辑可能存在瓶颈,需优化任务逻辑,而非盲目增加线程数。
2. 基于Spring Boot的动态调优实现
下面通过一个Spring Boot项目示例,演示如何实现线程池参数的动态调优。项目使用Maven管理依赖,包含Spring Boot、Spring Web、MyBatis Plus、Swagger 3、Lombok、Fastjson2等组件。
首先是pom.xml依赖配置:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>thread-pool-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>thread-pool-demo</name>
<description>Demo project for Spring Boot Thread Pool</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<fastjson2.version>2.0.43</fastjson2.version>
<springdoc.version>2.3.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.1.0-jre</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
接下来是线程池配置类,定义一个可动态调整的线程池:
package com.jam.demo.config;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池配置类
*
* @author ken
*/
@Slf4j
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程数
*/
private static final int CORE_POOL_SIZE = 5;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = 10;
/**
* 线程存活时间
*/
private static final long KEEP_ALIVE_TIME = 60L;
/**
* 工作队列容量
*/
private static final int QUEUE_CAPACITY = 100;
/**
* 创建可动态调整的线程池
*
* @return ThreadPoolExecutor实例
*/
@Bean("dynamicThreadPool")
public ThreadPoolExecutor dynamicThreadPool() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("dynamic-pool-%d")
.build();
return new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
然后是线程池动态调整服务类,提供参数调整和监控指标查询的方法:
package com.jam.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池动态调整服务
*
* @author ken
*/
@Slf4j
@Service
public class ThreadPoolService {
@Resource(name = "dynamicThreadPool")
private ThreadPoolExecutor threadPoolExecutor;
/**
* 调整核心线程数
*
* @param corePoolSize 新的核心线程数
*/
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize <= 0) {
throw new IllegalArgumentException("核心线程数必须大于0");
}
threadPoolExecutor.setCorePoolSize(corePoolSize);
log.info("核心线程数已调整为: {}", corePoolSize);
}
/**
* 调整最大线程数
*
* @param maximumPoolSize 新的最大线程数
*/
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize < threadPoolExecutor.getCorePoolSize()) {
throw new IllegalArgumentException("最大线程数不能小于核心线程数");
}
threadPoolExecutor.setMaximumPoolSize(maximumPoolSize);
log.info("最大线程数已调整为: {}", maximumPoolSize);
}
/**
* 调整线程存活时间
*
* @param keepAliveTime 新的存活时间
* @param unit 时间单位
*/
public void setKeepAliveTime(long keepAliveTime, TimeUnit unit) {
if (keepAliveTime < 0) {
throw new IllegalArgumentException("存活时间不能小于0");
}
threadPoolExecutor.setKeepAliveTime(keepAliveTime, unit);
log.info("线程存活时间已调整为: {} {}", keepAliveTime, unit);
}
/**
* 获取线程池监控指标
*
* @return 监控指标JSON字符串
*/
public String getThreadPoolMetrics() {
int corePoolSize = threadPoolExecutor.getCorePoolSize();
int maximumPoolSize = threadPoolExecutor.getMaximumPoolSize();
int poolSize = threadPoolExecutor.getPoolSize();
int activeCount = threadPoolExecutor.getActiveCount();
long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
int queueSize = threadPoolExecutor.getQueue().size();
int remainingCapacity = threadPoolExecutor.getQueue().remainingCapacity();
return String.format(
"{"corePoolSize":%d,"maximumPoolSize":%d,"poolSize":%d,"activeCount":%d," +
""completedTaskCount":%d,"queueSize":%d,"remainingCapacity":%d}",
corePoolSize, maximumPoolSize, poolSize, activeCount, completedTaskCount, queueSize, remainingCapacity
);
}
}
接下来是Controller类,提供HTTP接口用于动态调整参数和查询监控指标:
package com.jam.demo.controller;
import com.jam.demo.service.ThreadPoolService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 线程池动态调整接口
*
* @author ken
*/
@Tag(name = "线程池管理", description = "线程池参数动态调整与监控")
@RestController
@RequestMapping("/thread-pool")
public class ThreadPoolController {
@Resource
private ThreadPoolService threadPoolService;
/**
* 调整核心线程数
*
* @param corePoolSize 新的核心线程数
* @return 操作结果
*/
@Operation(summary = "调整核心线程数")
@PostMapping("/core-size")
public String setCorePoolSize(
@Parameter(description = "核心线程数", required = true)
@RequestParam int corePoolSize) {
threadPoolService.setCorePoolSize(corePoolSize);
return "核心线程数调整成功";
}
/**
* 调整最大线程数
*
* @param maximumPoolSize 新的最大线程数
* @return 操作结果
*/
@Operation(summary = "调整最大线程数")
@PostMapping("/max-size")
public String setMaximumPoolSize(
@Parameter(description = "最大线程数", required = true)
@RequestParam int maximumPoolSize) {
threadPoolService.setMaximumPoolSize(maximumPoolSize);
return "最大线程数调整成功";
}
/**
* 调整线程存活时间
*
* @param keepAliveTime 新的存活时间
* @param unit 时间单位(SECONDS/MINUTES等)
* @return 操作结果
*/
@Operation(summary = "调整线程存活时间")
@PostMapping("/keep-alive")
public String setKeepAliveTime(
@Parameter(description = "存活时间", required = true)
@RequestParam long keepAliveTime,
@Parameter(description = "时间单位", required = true)
@RequestParam String unit) {
TimeUnit timeUnit = TimeUnit.valueOf(unit.toUpperCase());
threadPoolService.setKeepAliveTime(keepAliveTime, timeUnit);
return "线程存活时间调整成功";
}
/**
* 获取线程池监控指标
*
* @return 监控指标JSON
*/
@Operation(summary = "获取线程池监控指标")
@GetMapping("/metrics")
public String getMetrics() {
return threadPoolService.getThreadPoolMetrics();
}
}
最后是Spring Boot启动类:
package com.jam.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author ken
*/
@SpringBootApplication
public class ThreadPoolDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ThreadPoolDemoApplication.class, args);
}
}
通过上述代码,我们可以通过Swagger UI(访问地址:http://localhost:8080/swagger-ui.html)调用接口,动态调整线程池参数,并查询监控指标。
3. 调优策略与监控指标
动态调优需要遵循一定的策略,避免盲目调整:
- 小步快跑:每次调整参数的幅度不宜过大,比如每次增加2-5个线程,观察一段时间后再决定是否继续调整。
- 结合业务:根据业务的高峰期和低谷期,提前调整参数。比如促销活动前,适当增加核心线程数和最大线程数;活动结束后,再调整回正常水平。
- 监控告警:设置监控告警规则,当队列大小超过阈值、活跃线程数接近最大线程数、任务拒绝次数增加时,及时发送告警,人工介入调整。
除了代码中提到的监控指标,还可以结合Spring Boot Actuator的metrics端点,采集更丰富的指标,如线程池的任务提交速率、任务完成速率等。
三、线程泄露排查与解决方案
1. 线程泄露的常见原因
线程泄露是指线程池中的线程因为某些原因无法正常回收,导致线程数持续增长,最终可能导致系统资源耗尽(如OOM)。常见原因包括:
- 线程未正确关闭:线程执行完任务后,没有正确退出,比如while(true)循环没有退出条件,或者阻塞在某个IO操作上无法返回。
- ThreadLocal内存泄漏:ThreadLocal变量没有被清理,导致线程无法被回收,同时ThreadLocal关联的对象也无法被回收,造成内存泄漏。
- 异常未被捕获:线程执行任务时抛出未捕获的异常,导致线程终止,但线程池没有及时创建新线程替换,或者线程因为异常状态无法被回收。
- 资源未释放:线程在执行任务时占用了某些资源(如数据库连接、文件句柄),没有正确释放,导致线程被资源占用,无法回收。
2. 排查工具与方法
排查线程泄露的常用工具包括:
- jstack:JDK自带的线程堆栈分析工具,用于查看线程的运行状态、堆栈信息。通过jstack可以发现是否有大量线程处于WAITING、TIMED_WAITING或BLOCKED状态,且堆栈信息显示线程阻塞在某个操作上。
- jmap:JDK自带的内存分析工具,用于查看堆内存的使用情况,结合jhat或MAT(Memory Analyzer Tool)可以分析是否有内存泄漏。
- Arthas:阿里巴巴开源的Java诊断工具,功能强大,可以实时查看线程状态、监控方法执行、查看ThreadLocal信息等。
排查步骤:
- 使用jstack命令生成线程堆栈快照,命令:
jstack <pid> > thread_dump.txt。 - 分析thread_dump.txt文件,查找是否有大量同名线程(如线程池中的线程)处于非RUNNABLE状态,且堆栈信息长期不变。
- 如果怀疑是ThreadLocal内存泄漏,可以使用Arthas的
thread -n <threadId>命令查看特定线程的ThreadLocal信息,或者使用MAT分析堆内存转储文件。 - 结合业务代码,查找可能导致线程阻塞或资源未释放的地方。
3. 解决方案与代码示例
针对不同的线程泄露原因,解决方案如下:
- 线程未正确关闭:检查线程的执行逻辑,确保有正确的退出条件,避免无限循环。对于IO操作,设置超时时间,避免线程长期阻塞。
- ThreadLocal内存泄漏:在使用完ThreadLocal后,及时调用remove()方法清理变量。可以在finally块中执行清理操作,确保即使发生异常也能清理。
- 异常未被捕获:在任务的run()方法中使用try-catch-finally块捕获所有异常,避免线程因异常终止。同时,可以利用ThreadPoolExecutor的afterExecute()方法处理异常,记录日志并创建新线程(如果需要)。
- 资源未释放:在finally块中释放资源,如关闭数据库连接、文件流等。
下面是一个模拟ThreadLocal内存泄漏及解决方案的代码示例:
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* ThreadLocal内存泄漏示例
*
* @author ken
*/
@Slf4j
public class ThreadLocalLeakDemo {
/**
* 模拟大对象
*/
static class BigObject {
private byte[] data = new byte[1024 * 1024]; // 1MB
}
/**
* ThreadLocal变量
*/
private static final ThreadLocal<BigObject> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("leak-pool-%d")
.build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
2,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
threadFactory
);
// 提交10个任务,每个任务设置ThreadLocal变量
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
// 设置ThreadLocal变量
THREAD_LOCAL.set(new BigObject());
log.info("ThreadLocal变量已设置,线程名: {}", Thread.currentThread().getName());
// 错误示例:没有清理ThreadLocal变量
// 正确示例:在finally块中清理
// try {
// // 执行业务逻辑
// } finally {
// THREAD_LOCAL.remove();
// log.info("ThreadLocal变量已清理,线程名: {}", Thread.currentThread().getName());
// }
});
}
// 等待任务执行完成
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// 此时线程池中的线程仍然存活,ThreadLocal变量未被清理,导致内存泄漏
log.info("任务执行完成,观察内存使用情况");
}
}
在上述代码中,错误示例没有清理ThreadLocal变量,导致线程池中的线程持有BigObject的引用,造成内存泄漏。正确示例在finally块中调用THREAD_LOCAL.remove()方法,清理ThreadLocal变量,避免内存泄漏。
四、任务堆积排查与解决方案
1. 任务堆积的根因分析
任务堆积是指工作队列中的任务数量持续增长,超过了线程池的处理能力,导致任务等待时间过长,影响业务性能。常见根因包括:
- 任务生产速度远大于消费速度:业务流量突增(如促销活动),任务提交速度过快,而线程池的处理能力不足,导致队列积压。
- 任务执行逻辑慢:单个任务的执行时间过长(如复杂的数据库查询、IO操作),导致线程池的吞吐量下降,队列积压。
- 线程池参数设置不合理:corePoolSize和maximumPoolSize设置过小,工作队列容量设置过大或过小,导致线程池无法及时处理任务。
- 线程池出现故障:线程池中的线程大量阻塞(如数据库连接池耗尽、网络故障),导致无法处理任务,队列积压。
2. 排查步骤与监控
排查任务堆积的步骤:
- 查看监控指标:通过线程池的监控指标(如队列大小、活跃线程数、任务执行时间),判断是否存在任务堆积。
- 分析任务执行时间:查看任务的平均执行时间和最大执行时间,判断是否是任务执行逻辑慢导致的堆积。
- 检查线程状态:使用jstack查看线程池中的线程状态,判断是否有大量线程处于阻塞状态(如BLOCKED、WAITING)。
- 检查外部依赖:查看数据库、缓存、网络等外部依赖是否正常,是否存在性能瓶颈。
监控方面,除了前面提到的线程池监控指标,还可以监控:
- 任务等待时间:任务从提交到开始执行的时间间隔。
- 任务生产速率与消费速率:单位时间内提交的任务数和完成的任务数。
- 外部依赖的响应时间:如数据库查询时间、API调用时间。
3. 解决方案与实战代码
针对不同的任务堆积原因,解决方案如下:
-
任务生产速度远大于消费速度:
- 限流:在任务提交端进行限流,控制任务提交速度,避免队列无限积压。
- 扩容:增加线程池的corePoolSize和maximumPoolSize,提高处理能力。
- 分布式处理:使用分布式任务调度框架(如XXL-Job、Elastic-Job),将任务分发到多个节点处理。
-
任务执行逻辑慢:
- 优化任务逻辑:优化数据库查询(如加索引、优化SQL)、减少IO操作、使用缓存。
- 任务拆分:将大任务拆分成小任务,提高并行处理效率。
-
线程池参数设置不合理:
- 调整参数:根据监控指标,合理调整corePoolSize、maximumPoolSize和队列容量。
-
线程池出现故障:
- 排查外部依赖:修复外部依赖的故障(如恢复数据库连接、解决网络问题)。
- 线程池隔离:将不同业务的线程池隔离,避免一个业务的故障影响其他业务。
下面是一个模拟任务堆积及解决方案的代码示例,包含任务提交、限流和动态调整参数:
package com.jam.demo;
import com.google.common.util.concurrent.RateLimiter;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 任务堆积示例
*
* @author ken
*/
@Slf4j
public class TaskBacklogDemo {
public static void main(String[] args) throws InterruptedException {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("backlog-pool-%d")
.build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
5,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 模拟任务生产速度过快,使用RateLimiter限流(每秒允许提交5个任务)
RateLimiter rateLimiter = RateLimiter.create(5.0);
// 提交100个任务
for (int i = 0; i < 100; i++) {
// 限流
rateLimiter.acquire();
final int taskId = i;
executor.submit(() -> {
log.info("开始执行任务,taskId: {}, 线程名: {}", taskId, Thread.currentThread().getName());
try {
// 模拟任务执行时间(100ms)
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("任务执行被中断,taskId: {}", taskId, e);
}
log.info("任务执行完成,taskId: {}", taskId);
});
// 每提交10个任务,查看一次监控指标
if ((i + 1) % 10 == 0) {
log.info("已提交{}个任务,队列大小: {}, 活跃线程数: {}, 线程总数: {}",
i + 1, executor.getQueue().size(), executor.getActiveCount(), executor.getPoolSize());
// 模拟动态调整参数:当队列大小超过10时,增加核心线程数
if (executor.getQueue().size() > 10 && executor.getCorePoolSize() < 5) {
int newCoreSize = executor.getCorePoolSize() + 1;
executor.setCorePoolSize(newCoreSize);
log.info("队列大小超过10,核心线程数调整为: {}", newCoreSize);
}
}
}
// 等待任务执行完成
executor.shutdown();
executor.awaitTermination(5, TimeUnit.MINUTES);
log.info("所有任务执行完成");
}
}
在上述代码中,我们使用Guava的RateLimiter进行限流,控制任务提交速度。同时,每提交10个任务查看一次监控指标,当队列大小超过10时,动态增加核心线程数,提高处理能力,避免任务堆积。
总结:Java线程池是并发编程中的重要工具,合理设置参数、动态调优、及时排查问题是保证线程池高效稳定运行的关键。