知识模块
☕ Java 知识模块
三、Java 并发编程
线程池

线程池

线程池是管理线程的容器,通过复用线程减少创建销毁开销,提高系统性能。

为什么使用线程池?

  1. 降低资源消耗:复用线程,减少创建销毁开销
  2. 提高响应速度:任务到达时无需等待线程创建
  3. 便于管理:统一控制线程数量、监控状态

ThreadPoolExecutor 核心参数

public ThreadPoolExecutor(
    int corePoolSize,      // 核心线程数
    int maximumPoolSize,   // 最大线程数
    long keepAliveTime,    // 空闲线程存活时间
    TimeUnit unit,         // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列
    ThreadFactory threadFactory,        // 线程工厂
    RejectedExecutionHandler handler    // 拒绝策略
)

参数详解

参数说明
corePoolSize核心线程数,即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut)
maximumPoolSize最大线程数,当队列满时创建临时线程
keepAliveTime临时线程空闲存活时间
workQueue等待执行的任务队列
threadFactory线程工厂,可自定义线程名称、优先级等
handler拒绝策略,队列满且线程数达到最大时触发

任务执行流程

提交任务

核心线程数未满? → 创建核心线程执行任务
   ↓ 是
加入队列

队列未满? → 加入队列等待
   ↓ 是
线程数未达最大? → 创建临时线程执行任务
   ↓ 是
执行拒绝策略

四大拒绝策略

策略类名行为
AbortPolicy默认策略抛出 RejectedExecutionException
CallerRunsPolicy调用者运行由提交任务的线程执行
DiscardPolicy直接丢弃静默丢弃任务
DiscardOldestPolicy丢弃最老丢弃队列最老任务,重新提交
// 自定义拒绝策略
executor.setRejectedExecutionHandler((r, executor) -> {
    // 记录日志
    log.warn("任务被拒绝: {}", r);
    // 持久化到数据库,后续重试
});

三种队列类型

队列类型说明适用场景
ArrayBlockingQueue有界队列资源有限,防止 OOM
LinkedBlockingQueue无界队列任务量不可预估时
SynchronousQueue直接提交高吞吐,每个任务立即执行

注意:无界队列可能导致 OOM,推荐使用有界队列。

Executors 工厂方法(不推荐生产使用)

方法说明问题
newFixedThreadPool固定线程数无界队列,可能 OOM
newSingleThreadExecutor单线程无界队列,可能 OOM
newCachedThreadPool可缓存线程池最大线程数 Integer.MAX_VALUE
newScheduledThreadPool定时任务无界队列

阿里规约:禁止使用 Executors 创建线程池,应通过 ThreadPoolExecutor 直接创建。

正确的线程池配置

// CPU 密集型:核心线程数 = CPU 核数 + 1
int cpuCount = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor cpuIntensive = new ThreadPoolExecutor(
    cpuCount + 1, cpuCount + 1,
    0L, TimeUnit.MILLISECONDS,
    new ArrayBlockingQueue<>(1000),
    new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);
 
// IO 密集型:核心线程数 = CPU 核数 * 2
ThreadPoolExecutor ioIntensive = new ThreadPoolExecutor(
    cpuCount * 2, cpuCount * 2,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(500),
    new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build(),
    new ThreadPoolExecutor.AbortPolicy()
);

线程池状态

状态说明
RUNNING运行中,接受新任务
SHUTDOWN不接受新任务,处理完队列任务
STOP不接受新任务,中断正在执行的任务
TIDYING所有任务终止,执行 terminated()
TERMINATEDterminated() 执行完毕
executor.shutdown();      // 平滑关闭
executor.shutdownNow();   // 立即关闭
executor.awaitTermination(60, TimeUnit.SECONDS);  // 等待终止

监控线程池

ThreadPoolExecutor executor = ...;
 
// 监控指标
int active = executor.getActiveCount();        // 活跃线程数
long completed = executor.getCompletedTaskCount();  // 完成任务数
int queueSize = executor.getQueue().size();    // 队列任务数
int poolSize = executor.getPoolSize();         // 当前线程数
 
// 自定义监控
executor.setTaskExecutionListener((task, duration) -> {
    if (duration > 1000) {
        log.warn("任务执行超过 1 秒: {}", task);
    }
});

线程池异常处理

线程池中的任务异常不会抛出到调用者,需要特殊处理:

// 方式一:任务内部 try-catch
executor.execute(() -> {
    try {
        // 业务逻辑
    } catch (Exception e) {
        log.error("任务执行异常", e);
    }
});
 
// 方式二:自定义线程工厂,设置异常处理器
ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler((thread, e) -> {
        log.error("线程 {} 异常", thread.getName(), e);
    });
    return t;
};

常见面试题

Q1: 核心线程数和最大线程数什么时候创建?

  • 核心线程:任务提交时,核心线程数未满则创建
  • 临时线程:队列满后,线程数未达最大则创建

Q2: 线程池如何保证线程安全?

线程池使用 ReentrantLock 保护状态变更,使用 CAS 更新线程数(通过 AtomicInteger 的 ctl 字段)。

Q3: 为什么不建议使用 Executors?

  1. FixedThreadPoolSingleThreadPool:无界队列可能 OOM
  2. CachedThreadPool:最大线程数无限制可能 OOM
  3. 无法自定义拒绝策略、线程名等

Q4: 如何合理配置线程池参数?

  • CPU 密集型:核心线程数 = CPU 核数 + 1
  • IO 密集型:核心线程数 = CPU 核数 * 2 或更高
  • 混合型:根据 IO 等待时间占比调整

小结

  • 线程池通过复用线程提高性能
  • 7 大参数:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler
  • 4 种拒绝策略:Abort、CallerRuns、Discard、DiscardOldest
  • 不推荐使用 Executors,应手动创建线程池
  • 使用有界队列防止 OOM
  • 根据任务类型合理配置线程数