第9章
🏊♂️ 线程池
掌握线程池的设计原理、核心参数配置和性能优化技巧
学习目标
- 理解线程池的设计原理和工作机制
- 掌握ThreadPoolExecutor的使用和配置
- 熟悉核心参数的含义和调优方法
- 了解各种拒绝策略的应用场景
- 学会线程池的监控和性能调优
线程池原理
线程池(Thread Pool)是一种线程使用模式,它预先创建一定数量的线程,并将这些线程放入池中进行管理。当有任务需要执行时,从池中取出空闲线程来执行任务,任务完成后线程返回池中等待下次使用。
为什么需要线程池
线程的创建和销毁是昂贵的操作,频繁创建销毁线程会消耗大量系统资源。线程池通过复用线程,避免了这种开销,同时提供了更好的线程管理和控制能力。
线程池的优势
提高性能
避免频繁创建销毁线程的开销,提高任务执行效率。
资源控制
限制并发线程数量,防止系统资源耗尽。
统一管理
提供统一的线程管理和监控机制。
线程池工作原理
- 任务提交:客户端向线程池提交任务
- 线程分配:线程池根据策略分配线程执行任务
- 任务执行:工作线程从任务队列中取出任务并执行
- 线程回收:任务完成后,线程返回池中等待下次使用
- 池管理:根据负载情况动态调整线程数量
ThreadPoolExecutor详解
ThreadPoolExecutor是Java中最核心的线程池实现,它提供了丰富的配置选项和灵活的扩展机制。理解其构造参数和工作流程是掌握线程池的关键。
构造参数详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数,即使空闲也会保留在池中的线程数量。
maximumPoolSize
最大线程数,池中允许的最大线程数量。
keepAliveTime
空闲线程的存活时间,超过核心线程数的线程在空闲时的最大存活时间。
workQueue
任务队列,用于存储等待执行的任务。
threadFactory
线程工厂,用于创建新线程。
handler
拒绝策略,当任务无法执行时的处理策略。
工作流程
执行流程
- 步骤1:如果当前线程数 < corePoolSize,创建新线程执行任务
- 步骤2:如果当前线程数 >= corePoolSize,将任务加入工作队列
- 步骤3:如果队列已满且当前线程数 < maximumPoolSize,创建新线程
- 步骤4:如果队列已满且当前线程数 >= maximumPoolSize,执行拒绝策略
核心参数配置
合理配置线程池参数是保证系统性能的关键。不同的应用场景需要不同的参数配置策略。
参数配置原则
CPU密集型任务
- corePoolSize = CPU核心数 + 1
- maximumPoolSize = CPU核心数 + 1
- 使用SynchronousQueue
IO密集型任务
- corePoolSize = CPU核心数 * 2
- maximumPoolSize = CPU核心数 * 2
- 使用LinkedBlockingQueue
混合型任务
- 根据任务特性动态调整
- 监控系统负载进行优化
- 使用ArrayBlockingQueue
队列选择策略
// 1. SynchronousQueue - 直接提交队列
ExecutorService executor1 = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new SynchronousQueue<>());
// 2. LinkedBlockingQueue - 无界队列
ExecutorService executor2 = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
// 3. ArrayBlockingQueue - 有界队列
ExecutorService executor3 = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100));
拒绝策略
当线程池无法处理新提交的任务时,会执行拒绝策略。Java提供了四种内置的拒绝策略,也支持自定义拒绝策略。
内置拒绝策略
AbortPolicy
默认策略,直接抛出RejectedExecutionException异常。
CallerRunsPolicy
由调用线程执行该任务,可以减缓新任务的提交速度。
DiscardPolicy
静默丢弃无法处理的任务,不抛出异常。
DiscardOldestPolicy
丢弃队列中最老的任务,然后重新提交当前任务。
自定义拒绝策略
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志
logger.warn("Task {} rejected from {}", r.toString(), executor.toString());
// 尝试放入备用队列
if (!backupQueue.offer(r)) {
// 如果备用队列也满了,则执行其他策略
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " + executor.toString());
}
}
}
线程池监控与调优
有效的监控是线程池调优的基础。通过监控关键指标,可以及时发现性能问题并进行优化。
关键监控指标
线程数指标
- 当前线程数:getPoolSize()
- 活跃线程数:getActiveCount()
- 最大线程数:getLargestPoolSize()
任务执行指标
- 已完成任务数:getCompletedTaskCount()
- 总任务数:getTaskCount()
- 队列大小:getQueue().size()
性能指标
- 任务执行时间
- 任务等待时间
- 线程利用率
监控实现示例
public class ThreadPoolMonitor {
private final ThreadPoolExecutor executor;
private final ScheduledExecutorService monitor;
public ThreadPoolMonitor(ThreadPoolExecutor executor) {
this.executor = executor;
this.monitor = Executors.newScheduledThreadPool(1);
startMonitoring();
}
private void startMonitoring() {
monitor.scheduleAtFixedRate(() -> {
logger.info("ThreadPool Status: " +
"Pool Size: {}, Active: {}, Completed: {}, Queue Size: {}",
executor.getPoolSize(),
executor.getActiveCount(),
executor.getCompletedTaskCount(),
executor.getQueue().size());
}, 0, 30, TimeUnit.SECONDS);
}
}
调优建议
- 合理设置核心线程数:根据CPU核心数和任务类型确定
- 选择合适的队列:根据任务特性选择队列类型和大小
- 设置合理的拒绝策略:根据业务需求选择或自定义拒绝策略
- 定期监控和调整:根据监控数据动态调整参数
- 避免无界队列:防止内存溢出