Java 线程池(Thread Pool)是 Java 并发编程中的重要组件,主要用于管理和复用线程,以提高程序的并发性能、减少线程创建和销毁的开销,并防止资源耗尽。Java 提供了 java.util.concurrent
包下的 Executor
框架来管理线程池。
1. 为什么要使用线程池?
在高并发环境下,如果每个任务都创建一个新线程:
- 创建和销毁线程的开销大:每次创建线程都需要分配资源,销毁时需要回收资源。
- 线程资源不可控:如果无限制创建线程,可能会导致系统资源耗尽,如 CPU 过载或内存溢出。
- 线程调度成本高:线程数过多,线程之间的切换(上下文切换)会增加 CPU 负担。
线程池的好处:
- 降低资源消耗:线程可复用,避免频繁创建和销毁线程。
- 提高响应速度:任务提交后直接复用已有线程,减少创建新线程的延迟。
- 提高可管理性:可以限制线程总数,防止无限制创建线程导致系统崩溃。
- 提供任务排队机制:任务可在队列中等待,避免任务过载时无限创建线程。
2. Java 线程池的核心类
Java 提供了 Executor
框架来管理线程池,其中主要的类包括:
Executor
(接口):是线程池的顶层接口,定义了执行任务的方法。ExecutorService
(接口):继承Executor
,提供更高级的线程池管理功能,如任务提交、关闭线程池等。ThreadPoolExecutor
(核心实现类):是ExecutorService
的主要实现类,可以自定义线程池的大小、任务队列、拒绝策略等。Executors
(工具类):提供了一些预定义的线程池工厂方法(如newFixedThreadPool()
)。
3. ThreadPoolExecutor
详解
ThreadPoolExecutor
是 Java 线程池的核心实现类,其构造方法如下:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程的存活时间
TimeUnit unit, // 存活时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
3.1 参数解析:
-
corePoolSize
(核心线程数)- 线程池中始终保持的线程数量,即使这些线程空闲也不会销毁。
- 任务提交时,如果当前线程数小于
corePoolSize
,则会创建新线程。
-
maximumPoolSize
(最大线程数)- 线程池中允许的最大线程数,只有当任务队列满了,且核心线程都在忙碌时,才会创建超过
corePoolSize
的线程。
- 线程池中允许的最大线程数,只有当任务队列满了,且核心线程都在忙碌时,才会创建超过
-
keepAliveTime
&unit
(非核心线程存活时间 & 时间单位)- 超过
corePoolSize
的线程(即非核心线程),如果在keepAliveTime
时间内没有任务执行,就会被销毁。 unit
指定时间单位,如TimeUnit.SECONDS
、TimeUnit.MILLISECONDS
。
- 超过
-
workQueue
(任务队列)- 存放等待执行的任务,常见的队列类型:
ArrayBlockingQueue
(有界队列):适用于任务量固定的场景,避免无限制堆积任务。LinkedBlockingQueue
(无界队列):适用于任务量不确定的场景,但可能导致maximumPoolSize
无效。SynchronousQueue
(直接交付队列):不存储任务,任务提交后必须有线程立即执行,否则会创建新线程。
- 存放等待执行的任务,常见的队列类型:
-
threadFactory
(线程工厂)- 用于创建线程,可以自定义线程名称等,默认使用
Executors.defaultThreadFactory()
。
- 用于创建线程,可以自定义线程名称等,默认使用
-
handler
(拒绝策略)- 当任务队列满了,且线程数已达
maximumPoolSize
时,处理新任务的策略:AbortPolicy
(默认策略):抛出RejectedExecutionException
,任务不会被执行。CallerRunsPolicy
:由提交任务的线程执行任务(即调用execute()
的线程自己执行)。DiscardPolicy
:丢弃任务,不抛异常。DiscardOldestPolicy
:丢弃队列中最老的任务,然后重新尝试执行新任务。
- 当任务队列满了,且线程数已达
4. 线程池的执行流程
-
任务提交到线程池:
- 如果当前线程数 <
corePoolSize
,创建新线程执行任务。 - 如果当前线程数 ≥
corePoolSize
,任务进入workQueue
等待执行。 - 如果
workQueue
满了,且当前线程数 <maximumPoolSize
,创建新线程执行任务。 - 如果
workQueue
满了,且当前线程数 ≥maximumPoolSize
,触发拒绝策略。
- 如果当前线程数 <
-
线程执行任务:
- 线程从队列取出任务并执行。
- 任务执行完后,线程继续获取下一个任务,或等待
keepAliveTime
时间。
-
线程池关闭:
shutdown()
:不接受新任务,等待已有任务执行完毕。shutdownNow()
:试图中断正在执行的任务,清空任务队列。
5. 线程池的常见使用方式
5.1 使用 ThreadPoolExecutor
自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
5.2 使用 Executors
创建线程池(不推荐)
Executors
提供了几个预定义的线程池:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
但 Executors
创建的线程池有一些缺陷,如 newFixedThreadPool()
和 newSingleThreadExecutor()
可能导致 OOM(任务队列是 LinkedBlockingQueue
,默认无界)。
6. 线程池的最佳实践
- 避免使用
Executors
创建线程池,改用ThreadPoolExecutor
显式指定参数。 - 合理设置
corePoolSize
和maximumPoolSize
:- CPU 密集型任务(如计算):
corePoolSize = CPU 核心数
,maximumPoolSize = CPU 核心数 + 1
。 - IO 密集型任务(如网络请求):
corePoolSize
和maximumPoolSize
可以适当调大,如CPU 核心数 * 2
。
- CPU 密集型任务(如计算):
- 合理选择任务队列:
- 短任务:
SynchronousQueue
- 任务量较大:
ArrayBlockingQueue
(设置合理大小) - 任务不确定:
LinkedBlockingQueue
(需注意 OOM 风险)
- 短任务:
- 合理选择拒绝策略,根据业务需求选择合适的拒绝处理方式。
- 手动关闭线程池,防止程序无法退出。
Java 动态线程池(Dynamic Thread Pool)详解
动态线程池指的是可以在运行时动态调整 ThreadPoolExecutor
的参数(如核心线程数、最大线程数、任务队列等),从而提高线程池的灵活性,适应不同负载需求。在 Java 业务场景中,工作负载通常是变化的,因此静态配置线程池可能导致资源浪费或性能瓶颈,而动态线程池能更好地应对这些问题。
1. 为什么需要动态线程池?
静态线程池在实际业务中存在以下问题:
- 负载变化导致资源浪费
- 业务高峰时,固定大小的线程池可能不足,导致任务堆积或超时。
- 业务低谷时,过多的空闲线程占用系统资源,降低整体性能。
- 任务队列积压
- 如果任务执行速度慢,而
workQueue
容量有限,则任务可能被拒绝。
- 如果任务执行速度慢,而
- 手动修改配置成本高
- 业务需求变化时,如果
corePoolSize
和maximumPoolSize
需要调整,通常需要重启应用,而动态调整可以避免这个问题。
- 业务需求变化时,如果
2. 线程池参数动态调整
在 ThreadPoolExecutor
运行过程中,可以动态调整以下参数:
setCorePoolSize(int corePoolSize)
→ 调整核心线程数setMaximumPoolSize(int maximumPoolSize)
→ 调整最大线程数setKeepAliveTime(long time, TimeUnit unit)
→ 调整非核心线程存活时间setRejectedExecutionHandler(RejectedExecutionHandler handler)
→ 更改拒绝策略
示例代码:动态调整线程池参数
import java.util.concurrent.*;
public class DynamicThreadPoolExample {
public static void main(String[] args) throws InterruptedException {
// 初始线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 提交任务
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务");
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
});
}
// 观察线程池状态
System.out.println("初始 corePoolSize:" + executor.getCorePoolSize());
System.out.println("初始 maximumPoolSize:" + executor.getMaximumPoolSize());
// 动态调整线程池参数
Thread.sleep(5000);
executor.setCorePoolSize(4);
executor.setMaximumPoolSize(10);
executor.setKeepAliveTime(5, TimeUnit.SECONDS);
System.out.println("修改后 corePoolSize:" + executor.getCorePoolSize());
System.out.println("修改后 maximumPoolSize:" + executor.getMaximumPoolSize());
executor.shutdown();
}
}
运行结果(部分日志):
pool-1-thread-1 正在执行任务
pool-1-thread-2 正在执行任务
初始 corePoolSize:2
初始 maximumPoolSize:5
修改后 corePoolSize:4
修改后 maximumPoolSize:10
说明:
setCorePoolSize(4)
使线程池的核心线程数增加,提高并发能力。setMaximumPoolSize(10)
允许更多线程在高负载时创建,防止任务堆积。setKeepAliveTime(5, TimeUnit.SECONDS)
让非核心线程更快回收,降低低负载时的资源占用。
3. 结合 ScheduledExecutorService
定时调整线程池
可以使用 定时任务 来定期检测系统负载,并动态调整线程池大小。例如,可以根据 CPU 使用率、任务队列长度等指标调整 corePoolSize
和 maximumPoolSize
。
示例代码:定时调整线程池
import java.util.concurrent.*;
public class AdaptiveThreadPool {
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
// 定时任务,每 5 秒检查任务队列长度,动态调整线程池大小
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleAtFixedRate(() -> adjustThreadPool(), 0, 5, TimeUnit.SECONDS);
// 提交大量任务
for (int i = 0; i < 50; i++) {
executor.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 执行任务");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
private static void adjustThreadPool() {
int queueSize = executor.getQueue().size();
System.out.println("当前队列大小:" + queueSize);
if (queueSize > 5) {
executor.setCorePoolSize(Math.min(executor.getCorePoolSize() + 1, executor.getMaximumPoolSize()));
System.out.println("增大 corePoolSize: " + executor.getCorePoolSize());
} else if (queueSize == 0 && executor.getCorePoolSize() > 2) {
executor.setCorePoolSize(Math.max(executor.getCorePoolSize() - 1, 2));
System.out.println("减少 corePoolSize: " + executor.getCorePoolSize());
}
}
}
运行效果
当任务量增加,队列变大时,线程池自动扩容;任务减少时,线程池自动缩容,避免资源浪费。
4. 配合 Spring Boot
读取动态配置
在 Spring Boot
中,可以使用 @Scheduled
任务动态调整线程池,并从配置中心(如 nacos
、Apollo
)获取最新配置。
示例:Spring Boot 结合 @Scheduled
调整线程池
@Component
public class DynamicThreadPoolManager {
@Autowired
private ThreadPoolTaskExecutor executor;
@Scheduled(fixedRate = 5000)
public void adjustThreadPool() {
int activeCount = executor.getActiveCount();
int queueSize = executor.getThreadPoolExecutor().getQueue().size();
System.out.println("当前活动线程:" + activeCount + ",任务队列:" + queueSize);
if (queueSize > 5) {
executor.setCorePoolSize(executor.getCorePoolSize() + 1);
System.out.println("增加 corePoolSize:" + executor.getCorePoolSize());
} else if (queueSize == 0 && executor.getCorePoolSize() > 2) {
executor.setCorePoolSize(Math.max(executor.getCorePoolSize() - 1, 2));
System.out.println("减少 corePoolSize:" + executor.getCorePoolSize());
}
}
}
5. 总结
方式 | 适用场景 | 适用方法 |
---|---|---|
手动调用 setCorePoolSize() | 需要临时调整 | executor.setCorePoolSize(x) |
定时任务调整 | 负载随时间变化 | ScheduledExecutorService |
Spring Boot + 配置中心 | 业务需求随配置变化 | 监听配置变更,调用 setCorePoolSize() |
最佳实践
- 使用
ThreadPoolExecutor
代替Executors
创建线程池,避免 OOM。 - 定时监控线程池负载,自动调整
corePoolSize
,提升吞吐量。 - Spring Boot 结合配置中心,实时修改线程池参数,无需重启服务。
动态线程池适用于负载变化的场景,如 Web 服务器、微服务、高并发任务处理等。
ThreadPoolTaskExecutor 和 ThreadPoolExecutor 有什么区别
ThreadPoolTaskExecutor
和 ThreadPoolExecutor
都是 Java 线程池,但它们的应用场景、实现方式和功能有所不同。
对比项 | ThreadPoolExecutor | ThreadPoolTaskExecutor |
---|---|---|
包 | java.util.concurrent | org.springframework.scheduling.concurrent |
来源 | JDK 原生线程池 | Spring 封装的线程池 |
创建方式 | 直接 new ThreadPoolExecutor(...) | Spring 配置 @Bean 或 new ThreadPoolTaskExecutor() |
适用场景 | 适用于所有 Java 应用(通用) | 主要用于 Spring 框架的异步任务 |
扩展性 | 需要手动管理线程池 | 支持 Spring 事件、生命周期管理 |
执行任务方式 | execute(Runnable) / submit(Callable) | execute(Runnable) / submit(Callable) ,并支持 @Async |
1. ThreadPoolExecutor
(JDK 原生线程池)
ThreadPoolExecutor
是 Java 提供的核心线程池类,适用于所有 Java 应用程序,并且支持手动配置线程池参数。
示例:创建 ThreadPoolExecutor
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});
}
executor.shutdown();
}
}
特点
- 线程池参数可以完全自定义,例如核心线程数、最大线程数、任务队列等。
- 适用于所有 Java 应用,而不仅限于 Spring。
- 需要手动管理线程池的生命周期(调用
shutdown()
关闭)。
2. ThreadPoolTaskExecutor
(Spring 线程池封装)
ThreadPoolTaskExecutor
是 Spring 基于 ThreadPoolExecutor
的封装,主要用于 Spring 框架,支持 @Async
、生命周期管理,并能与 Spring 事件机制结合。
示例:Spring Boot 配置 ThreadPoolTaskExecutor
1)Java 配置方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(10);
executor.setThreadNamePrefix("async-task-");
executor.initialize(); // 初始化线程池
return executor;
}
}
2)使用 @Async
异步执行任务
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void executeAsyncTask() {
System.out.println(Thread.currentThread().getName() + " 执行异步任务");
}
}
3)开启 @Async
支持
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync // 启用异步
public class SpringBootApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootApp.class, args);
}
}
特点
- 与 Spring 兼容:自动管理线程池,支持
@Async
异步任务。 - Spring 容器管理:不需要手动关闭线程池,Spring 会自动管理生命周期。
- 支持
Executor
接口:可用作 Spring 的@Async
任务执行器。
3. ThreadPoolExecutor
和 ThreadPoolTaskExecutor
的主要区别
对比项 | ThreadPoolExecutor | ThreadPoolTaskExecutor |
---|---|---|
创建方式 | new ThreadPoolExecutor(...) 手动创建 | 通过 Spring @Bean 方式创建 |
适用范围 | 任意 Java 应用 | 适用于 Spring 项目 |
任务提交 | execute(Runnable) 或 submit(Callable) | 兼容 JDK 方式,并支持 @Async |
线程池管理 | 需手动关闭(shutdown() ) | 由 Spring 管理,自动关闭 |
生命周期 | 线程池独立运行 | 受 Spring 容器管理 |
监控支持 | 需手动实现监控 | 可结合 Spring 监控 |
4. 什么时候用 ThreadPoolExecutor
,什么时候用 ThreadPoolTaskExecutor
?
使用 ThreadPoolExecutor
✅ 适用场景
- 纯 Java 应用(不依赖 Spring)。
- 需要完全自定义线程池参数。
- 需要手动管理线程池的生命周期。
🚫 不适用场景
- Spring 项目中异步任务(建议使用
ThreadPoolTaskExecutor
)。
使用 ThreadPoolTaskExecutor
✅ 适用场景
- Spring Boot / Spring Cloud 项目,使用
@Async
进行异步任务执行。 - 需要由 Spring 容器管理线程池,避免手动管理。
- 需要与 Spring 事件监听结合(如
ApplicationEvent
)。
🚫 不适用场景
- 需要完全手动控制线程池(如自定义拒绝策略)。