一、什么是线程池
线程池是一种池化技术,用于预先创建并管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度。它的七大核心参数包括:核心线程数、最大线程数、空闲存活时间、单位时间、任务队列、线程工厂和拒绝策略。
-
corePoolSize:核心线程数,即线程池中始终保持的线程数量。
-
maximumPoolSize:最大线程数,即线程池中允许的最大线程数量。
-
keepAliveTime:线程空闲时间,超过这个时间的非核心线程会被销毁。
-
workQueue:任务队列,存放待执行的任务。
-
hreadFactory:线程工厂,用于创建新线程。
-
rejectedExecutionHandler:任务拒绝处理器,当任务无法执行时的处理策略。
二、工作原理
-
默认情况下线程不会预创建,任务提交之后才会创建线程(可以通过设置 prestartAllCoreThreads 预创建核心线程)。
-
当核心线程满了之后不会新建线程,而是把任务堆积到任务队列中。
-
如果任务队列放不下了,然后才会新增线程,直至达到最大线程数。
-
如果任务队列满了,然后也已经达到最大线程数了,这时候来任务会执行拒绝策略。
-
如果线程空闲时间超过空闲存活时间,并且当前线程数大于核心线程数的则会销毁线程,直到线程数等于核心线程数(设置 allowCoreThreadTimeOut 为 true 可以回收核心线程,默认为 false)。
图解:
-
任务提交,线程池线程数还未达到核心线程数:
-
核心线程数已满,任务队列未满的情况:
-
核心线程数已满,任务队列已满的情况:
-
线程池中线程数已达最大线程数的情况:
注意:核心线程和非核心线程在线程池中是一样的,并没有特殊的标识区分!
三、任务队列
队列类型 | 行为 | 适用场景 |
---|---|---|
LinkedBlockingQueue | 无界或有界队列,FIFO 顺序 | 固定大小的线程池(如 FixedThreadPool ) |
ArrayBlockingQueue | 有界队列,FIFO 顺序,需指定容量 | 控制任务积压,避免资源耗尽 |
SynchronousQueue | 不存储任务,直接将任务交给线程。若无线程可用,则创建新线程或拒绝任务 | 高吞吐、任务处理快的场景(如 CachedThreadPool ) |
PriorityBlockingQueue | 按优先级排序的无界队列 | 需要任务优先级调度的场景 |
DelayQueue | 存储延迟任务,任务按到期时间排序 | 定时任务或延迟任务调度 |
四、拒绝策略
策略 | 行为 | 适用场景 |
---|---|---|
AbortPolicy(默认) | 直接抛出异常 | 需要严格保证任务不丢失 |
CallerRunsPolicy | 用提交任务的线程执行 | 允许降级执行,避免任务堆积 |
DiscardPolicy | 静默丢弃 | 可容忍任务丢失 |
DiscardOldestPolicy | 丢弃队列最旧任务 | 优先处理新任务 |
源码分析
-
AbortPolicy
// 定义一个静态内部类 AbortPolicy,实现拒绝任务处理策略 public static class AbortPolicy implements RejectedExecutionHandler { /** * 默认构造方法,无特殊初始化逻辑 * 该策略实例化时不需要任何参数或状态设置 */ public AbortPolicy() { // 空构造器,因为该策略无需维护内部状态 } /** * 当线程池无法接受新任务时,直接抛出 RejectedExecutionException * 这是标准的"中止策略"实现,强制调用方处理任务被拒绝的情况 * * @param r 被拒绝执行的任务对象(Runnable) * @param e 触发拒绝操作的线程池实例(ThreadPoolExecutor) * @throws RejectedExecutionException 始终抛出,包含任务和线程池的详细信息 * * 实现细节: * - 使用 throw 立即中断执行流程 * - 异常信息包含被拒任务的 toString() 和线程池的 toString() * - 适用于需要严格处理任务提交失败的场景 * - 相比其他策略(如静默丢弃),更有利于问题排查 */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); // 注意: // 1. 需要确保调用方有异常处理机制 // 2. 任务对象的 toString() 应实现有意义的描述 // 3. 线程池的 toString() 通常包含状态信息(如 poolSize, activeCount 等) } }
-
CallerRunsPolicy
// 定义静态内部类 CallerRunsPolicy,实现一种温和的拒绝策略 public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * 默认构造方法,无需特殊初始化 * 该策略不依赖任何内部状态 */ public CallerRunsPolicy() { // 空构造器,符合无状态设计 } /** * 当线程池无法接受新任务时,由调用者线程直接执行任务 * 这是一种流量整形机制,通过降低任务提交速度保护系统 * * @param r 被拒绝执行的任务对象 * @param e 触发拒绝操作的线程池实例 * * 关键逻辑: * 1. 检查线程池是否处于SHUTDOWN状态(避免执行已关闭池的任务) * 2. 若线程池仍活跃,则用当前提交线程同步执行任务 * 3. 实际效果相当于将线程池的"提交线程"临时转变为"工作线程" */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 防御性检查:确保线程池未关闭 if (!e.isShutdown()) { // 关键行为:直接在调用者线程执行任务(同步阻塞) r.run(); // 注意: // - 此处调用的是 run() 而非 start(),意味着同步执行 // - 可能导致提交线程(如主线程/网络IO线程)阻塞 // - 适用于能接受短时阻塞的任务场景 } // 若线程池已关闭,则静默丢弃任务(符合线程池生命周期规范) } }
-
DiscardPolicy
// 定义静态内部类 DiscardPolicy,实现静默丢弃任务的拒绝策略 public static class DiscardPolicy implements RejectedExecutionHandler { /** * 默认构造方法,无状态初始化 * 该策略不需要任何配置参数 */ public DiscardPolicy() { // 空构造器符合无状态设计原则 } /** * 当线程池无法接受新任务时,直接丢弃被拒绝的任务 * 该策略不会给出任何通知或反馈,属于静默失败模式 * * @param r 被拒绝的任务对象(Runnable) * @param e 触发拒绝操作的线程池实例(ThreadPoolExecutor) * * 实现特点: * - 方法体为空,不执行任何操作 * - 不检查线程池状态(即使线程池未关闭也直接丢弃) * - 适用于允许任务丢失的非关键场景 * * 风险提示: * - 任务丢失可能导致业务逻辑不完整 * - 无日志记录,问题排查困难 */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 空实现:被拒绝的任务直接被丢弃 // 注意:此处没有调用r.run()也没有抛出异常 } }
-
DiscardOldestPolicy
// 定义静态内部类 DiscardOldestPolicy,实现丢弃队列最旧任务的拒绝策略 public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * 默认构造方法,无需特殊初始化 * 策略本身不维护内部状态 */ public DiscardOldestPolicy() { // 空构造器符合无状态设计 } /** * 当线程池无法接受新任务时,丢弃工作队列中的最旧任务并重试提交新任务 * 该策略实现了任务队列的滚动更新机制 * * @param r 当前被拒绝的任务对象 * @param e 触发拒绝操作的线程池实例 * * 核心逻辑: * 1. 检查线程池是否处于SHUTDOWN状态(防御性编程) * 2. 从工作队列头部移除一个任务(FIFO策略) * 3. 尝试重新提交当前任务(可能成功或再次触发拒绝策略) * * 注意事项: * - 队列类型影响行为(对PriorityQueue等特殊队列可能不符合预期) * - 存在任务饥饿风险(持续提交可能导致旧任务永远无法执行) */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { // 移除队列头部元素(最旧任务) e.getQueue().poll(); // 注意:此处使用非阻塞的poll() // 尝试重新提交当前任务(可能成功或再次触发拒绝策略) e.execute(r); /* 潜在问题: * 1. 当队列持续满载时,可能进入【丢弃旧任务->提交新任务->再次被拒】的死循环 * 2. 被丢弃的任务不会得到任何通知(需确保业务容忍数据丢失) * 3. 多线程竞争环境下可能产生非公平调度 */ } } }
五、常见线程池实现方式
实现方式 | 描述 | 适用场景 |
---|---|---|
FixedThreadPool | 创建一个固定数量的线程池。线程池中的线程数是固定的,空闲的线程会被复用。如果所有线程都在忙,则新任务会放入队列中等待 | 适合负载稳定的场景,任务数量确定且不需要动态调整线程数 |
CachedThreadPool | 一个可以根据需要创建新线程的线程池。线程池的线程数量没有上限,空闲线程会在 60 秒后被回收,如果有新任务且没有可用线程,会创建新线程 | 适合短期大量并发任务的场景,任务执行时间短且线程数需求变化较大。 |
SingleThreadExecutor | 创建一个只有单个线程的线程池。只有一个线程处理任务,任务会按照提交顺序依次执行 | 适用于需要保证任务按顺序执行的场景,或者不需要并发处理任务的情况 |
ScheduledThreadPool | 支持定时任务和周期性任务的线程池。可以定时或以固定频率执行任务,线程池大小可以由用户指定 | 适用于需要周期性任务执行的场景,如定时任务调度器 |
WorkStealingPool | 基于任务窃取算法的线程池。线程池中的每个线程维护一个双端队列(deque),线程可以从自己的队列中取任务执行。如果线程的任务队列为空,它可以从其他线程的队列中“窃取”任务来执行,达到负载均衡的效果 | 适合大量小任务并行执行,特别是递归算法或大任务分解成小任务的场景 |