Java面试必问系列之线程池

一、线程池核心设计思想

1.1 为什么需要线程池?

  • 资源消耗大:直接创建线程(new Thread())会带来内存和调度开销
  • 稳定性风险:频繁创建/销毁可能导致 OOM 或系统过载
  • 性能瓶颈:线程切换开销在高并发场景下显著增加

1.2 线程池核心价值

维度直接创建线程线程池
资源开销高(每次创建新线程)低(复用线程)
响应速度慢(冷启动时间)快(直接复用空闲线程)
系统稳定性差(易 OOM)高(可控的线程数量)
任务调度支持队列/优先级调度

1.3 默认的线程池

方法底层实现(ThreadPoolExecutor 参数)适用场景风险点
newFixedThreadPoolcorePoolSize = maximumPoolSize固定并发量无界队列(LinkedBlockingQueue)可能导致 OOM
newCachedThreadPoolcorePoolSize=0maxPoolSize=Integer.MAX_VALUE短期突发任务线程数爆炸(可能创建数百万线程)
newSingleThreadExecutor单线程 FixedThreadPool顺序执行任务单点故障
newScheduledThreadPool支持定时/周期性任务定时任务调度线程泄漏风险
FixedThreadPool
// 底层实现
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThreads, 
        nThreads, 
        0L, 
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>() // 无界队列
    );
}
  • 特点
    • 核心线程数 = 最大线程数(固定线程数)
    • 使用无界队列(LinkedBlockingQueue
  • 适用场景
    Web 服务器处理 HTTP 请求(固定并发量)
  • 风险
    队列无限增长可能导致内存溢出(OOM)
CachedThreadPool
// 底层实现
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0, 
        Integer.MAX_VALUE, // 最大线程数极大
        60L, 
        TimeUnit.SECONDS,
        new SynchronousQueue<>() // 同步移交队列
    );
}
  • 特点
    • 核心线程数为 0,任务直接创建新线程执行
    • 空闲线程 60 秒后回收
  • 适用场景
    短期突发任务(如 RPC 请求处理)
  • 风险
    长时间高并发会导致线程数爆炸(如提交 100 万任务会创建 100 万线程)
SingleThreadExecutor
// 底层实现
public static ExecutorService newSingleThreadExecutor() {
    return new ThreadPoolExecutor(
        1, 
        1, 
        0L, 
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>() // 无界队列
    );
}
  • 特点
    • 严格单线程执行(保证任务顺序性)
    • 使用无界队列
  • 适用场景
    日志记录、事件监听等顺序敏感任务
  • 风险
    单线程阻塞会导致整个系统停滞
ScheduledThreadPool
// 底层实现
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// 内部实现类参数
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
          new DelayedWorkQueue());
}
  • 特点
    • 支持定时/周期性任务调度
    • 使用 DelayedWorkQueue 作为任务队列
  • 适用场景
    定时数据备份、周期性清理任务
  • 风险
    若任务执行时间超过间隔时间,可能导致任务堆积
核心问题总结
问题类型具体表现解决方案
资源耗尽OOM(尤其是 CachedThreadPool)使用有界队列 + 限制最大线程数
队列无限增长内存压力大改用 ArrayBlockingQueue
线程泄漏未正确关闭导致资源无法释放通过 shutdown() 优雅关闭
任务饥饿高优先级任务被低优先级任务阻塞使用优先级队列(PriorityBlockingQueue
生产环境替代方案
​通用型线程池配置
// 推荐的自定义配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10,                                  // 核心线程数
    50,                                  // 最大线程数
    60L,                                 // 空闲线程存活时间
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1000),      // 有界队列
    new CustomThreadFactory(),           // 自定义线程工厂
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
​CPU 密集型任务配置
int cpuCore = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor cpuExecutor = new ThreadPoolExecutor(
    cpuCore, 
    cpuCore + 1, 
    60L, 
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100) // 小队列减少上下文切换
);
​IO 密集型任务配置
// 假设每个任务耗时 10ms,可支撑 100 并发
ThreadPoolExecutor ioExecutor = new ThreadPoolExecutor(
    100, 
    200, 
    60L, 
    TimeUnit.SECONDS,
    new SynchronousQueue<>()); // 直接创建线程处理

实际工程中强烈建议直接使用 ThreadPoolExecutor 自定义参数

二、ThreadPoolExecutor 核心参数(重点)

2.1 参数全景图

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

2.2 参数深度解析

​(1) 核心线程数(corePoolSize)​
  • 关键特性
    • 即使空闲也不会被回收(除非设置 allowCoreThreadTimeOut=true
    • 线程池长期保持的最小线程数量
  • 配置建议
    // CPU密集型任务(如计算)
    int core = Runtime.getRuntime().availableProcessors() + 1;
    
    // IO密集型任务(如网络请求)
    int core = Runtime.getRuntime().availableProcessors() * 2;
​(2) 任务队列(workQueue)​
队列类型特性适用场景
ArrayBlockingQueue有界队列,FIFO,需要指定容量严格控制任务积压
LinkedBlockingQueue无界队列(默认容量 Integer.MAX_VALUE),可能导致 OOM任务量可预测的场景
SynchronousQueue同步移交队列,不存储任务,直接传递给线程需要立即执行的任务
PriorityBlockingQueue优先级队列,支持 Comparable 任务排序需要优先处理的任务场景
​(3) 拒绝策略(handler)​
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
策略行为特点适用场景
AbortPolicy直接抛出 RejectedExecutionException需要明确感知任务拒绝的场景
CallerRunsPolicy由提交任务的线程直接执行该任务平缓降级,避免雪崩效应
DiscardPolicy静默丢弃任务可丢失任务的场景
DiscardOldestPolicy丢弃队列中最旧的任务,尝试重新提交当前任务实时性要求高的场景

三、线程池工作原理解析

3.1 核心流程图解

步骤1: 提交任务 → [队列状态: 空, 线程数: 0]

步骤2: 检测核心线程数 → 未满 → 创建核心线程

步骤3: 核心线程执行 → [活跃线程: 1, 队列: 空]

步骤4: 持续提交任务 → 核心线程占满 → 任务入队

步骤5: 队列填满 → 创建非核心线程

步骤6: 非核心线程执行 → [活跃线程: maxPoolSize]

步骤7: 继续提交 → 达到上限 → 触发拒绝策略

3.2 关键执行步骤

阶段1:线程创建
// 核心线程创建逻辑
private boolean addWorker(Runnable firstTask, boolean core) {
    // 1. 检查线程池状态
    // 2. CAS 增加工作线程数
    // 3. 创建 Worker 对象
    // 4. 启动线程执行任务
}
阶段2:任务执行
// Worker 线程主循环
final void runWorker(Worker w) {
    Runnable task = w.firstTask;
    while (task != null || (task = getTask()) != null) {
        try {
            task.run(); // 执行任务
        } finally {
            task = null;
        }
    }
}

// 获取任务逻辑(关键超时控制)
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // 判断是否超时回收线程
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            
        if (r != null) return r;
        timedOut = true;
    }
}
阶段3:线程回收
  • 核心线程:默认永不过期(除非设置 allowCoreThreadTimeOut=true
  • 非核心线程:空闲时间超过 keepAliveTime 后被回收

四、线程池状态机

4.1 状态编码原理

  • ctl变量:AtomicInteger 类型,高3位表示状态,低29位表示线程数
  • 状态编码表
    状态名称值(十六进制)二进制掩码说明
    RUNNING-5368709121110 0000 ...接收新任务并处理队列任务
    SHUTDOWN00000 0000 ...不接收新任务,处理队列任务
    STOP5368709121110 0000 ...不接收新任务,中断执行中任务
    TIDYING10737418241000 0000 ...所有任务终止
    TERMINATED16106127361100 0000 ...终止方法执行完成

4.2 状态流转图

    [*] --> RUNNING
    RUNNING --> SHUTDOWN: 调用 shutdown()
    RUNNING --> STOP: 调用 shutdownNow()
    SHUTDOWN --> TIDYING: 队列空 + 无线程
    STOP --> TIDYING: 无线程
    TIDYING --> TERMINATED: terminated() 执行完成
转换路径触发条件
RUNNING → SHUTDOWN显式调用 shutdown() 方法
RUNNING → STOP显式调用 shutdownNow() 方法
SHUTDOWN → TIDYING满足以下两个条件:
① 任务队列为空
② 工作线程数为0
STOP → TIDYING工作线程数为0
TIDYING → TERMINATED执行 terminated() 钩子方法完成(默认空实现,可自定义逻辑)
状态接收新任务处理队列任务中断执行中任务典型场景
RUNNING正常处理请求
SHUTDOWN优雅关闭前处理积压任务
STOP紧急停止所有操作
TIDYING执行资源释放前的清理工作
TERMINATED线程池完全终止

五、实战应用案例

5.1 案例1:电商秒杀系统

// 高并发场景配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    50,                                 // 核心线程数(初始处理能力)
    200,                                // 最大线程数(应对流量峰值)
    60L,                                // 空闲线程存活时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000),    // 缓冲队列(削峰填谷)
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程兜底
);

// 模拟10万次秒杀请求
for (int i = 0; i < 100_000; i++) {
    final int taskId = i;
    executor.execute(() -> {
        try {
            processSeckill(taskId); // 秒杀核心逻辑
        } catch (Exception e) {
            log.error("秒杀失败", e);
        }
    });
}

5.2 案例2:异步日志处理

// 使用单线程池保证日志顺序性
ExecutorService logExecutor = Executors.newSingleThreadExecutor();

public void log(String message) {
    logExecutor.submit(() -> {
        try (PrintWriter writer = new PrintWriter(new FileWriter("app.log", true))) {
            writer.println(LocalDateTime.now() + " - " + message);
        } catch (IOException e) {
            // 异常处理
        }
    });
}

5.3 案例3:定时任务调度

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);

// 延迟2秒后执行,每5秒执行一次
scheduler.scheduleAtFixedRate(() -> {
    backupDatabase(); // 数据库备份任务
}, 2, 5, TimeUnit.SECONDS);

六、最佳实践与避坑指南

6.1 参数配置原则

  1. 避免无界队列LinkedBlockingQueue 可能导致内存溢出
  2. 合理设置拒绝策略
    • 生产环境推荐 CallerRunsPolicy 或自定义策略
    • 避免默认的 AbortPolicy(直接抛出异常)
  3. 线程命名混乱,难以排查问题​,推荐线程工厂规范
    static class CustomThreadFactory implements ThreadFactory {
        private final AtomicInteger threadNum = new AtomicInteger(1);
        
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "service-thread-" + threadNum.getAndIncrement());
            t.setUncaughtExceptionHandler((thread, throwable) -> {
                log.error("线程 {} 异常终止", thread.getName(), throwable);
            });
            return t;
        }
    }

6.2 监控与调优

// 监控关键指标
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
    System.out.printf("[监控] 活跃线程数: %d, 队列大小: %d, 已完成任务: %d%n",
        executor.getActiveCount(),
        executor.getQueue().size(),
        executor.getCompletedTaskCount()
    );
}, 0, 5, TimeUnit.SECONDS);

6.3 优雅关闭

// 优雅停机的正确姿势
executor.shutdown(); // 停止接收新任务
try {
    if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
        executor.shutdownNow(); // 强制终止
        if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
            log.error("线程池强制终止失败");
        }
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}

6.4线程池参数动态调整

// 动态修改核心线程数(需通过反射实现)
Field field = ThreadPoolExecutor.class.getDeclaredField("corePoolSize");
field.setAccessible(true);
field.set(executor, newCorePoolSize);

七、高阶特性扩展

7.1 任务窃取(Work-Stealing)

// ForkJoinPool 示例(自动实现任务窃取)
ForkJoinPool pool = new ForkJoinPool();
pool.submit(() -> {
    IntStream.range(0, 100)
        .parallel() // 自动拆分任务
        .forEach(System.out::println);
});

7.2 动态线程池(Alibaba Sentinel)

// 动态调整线程池参数(需结合监控系统)
DynamicThreadPoolExecutor dynamicExecutor = new DynamicThreadPoolExecutor(
    corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>(),
    new DynamicRejectedExecutionHandler() // 动态拒绝策略
);

// 通过API动态调整参数
dynamicExecutor.setMaximumPoolSize(200);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值