第9章

🏊‍♂️ 线程池

掌握线程池的设计原理、核心参数配置和性能优化技巧

学习目标

线程池原理

线程池(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核心数和任务类型确定
  • 选择合适的队列:根据任务特性选择队列类型和大小
  • 设置合理的拒绝策略:根据业务需求选择或自定义拒绝策略
  • 定期监控和调整:根据监控数据动态调整参数
  • 避免无界队列:防止内存溢出
上一章:原子类 返回目录 下一章:Executors工具类