线程池
拒接策略
线程池工作队列满了有哪些拒接策略?
- AbortPolicy (中止策略) - 行为:直接抛出 - RejectedExecutionException异常中断任务提交
- 适用场景:对任务完整性要求极高的场景(如金融交易),需配合异常捕获机制 
- 风险:未捕获异常可能导致程序中断 
 
- CallerRunsPolicy (调用者运行策略) - 行为:将任务回退到提交任务的线程(如主线程)直接执行 
- 适用场景:生产速度远高于消费速度时自然限流,允许短暂阻塞主线程的非实时任务 
- 注意事项:避免关键线程(如 UI 主线程)被阻塞 
 
- DiscardPolicy (静默丢弃策略) - 行为:无提示丢弃新任务,不抛异常也不执行 
- 适用场景:允许任务丢失的非关键场景(如实时监控采样) 
- 风险:需监控丢弃任务量,避免数据不一致 
 
- DiscardOldestPolicy (弃老策略) - 行为:丢弃队列中最旧任务,重试提交当前新任务 
- 适用场景:新任务优先级高于旧任务(如实时消息覆盖历史数据) 
- 风险:可能丢弃重要旧任务,需评估业务容忍度 
 
- 自定义拒绝策略 - 实现方式:实现 - RejectedExecutionHandler接口扩展逻辑
- 典型方案: - 持久化存储:任务存入数据库/Redis 后续处理 
- 异步重试:延迟后重新提交或转移至备用线程池 
- 告警通知:触发监控并记录任务详情 
 
- 适用场景:需保障任务最终执行的业务(如订单支付) 
 
- 策略选择原则 - 任务重要性:关键任务优先用 - CallerRunsPolicy或自定义策略
- 系统容忍度:允许丢失用 - DiscardPolicy,需时效性用- DiscardOldestPolicy
- 资源限制:内存敏感场景避免无界队列,明确队列容量 
- 监控配套:静默丢弃策略需加强队列长度和拒绝次数监控 
 
核心线程数
核心线程数设置为 0 可不可以?
- 可以设置但需谨慎权衡 - 技术上允许将核心线程数设为 0,线程池通过非核心线程处理任务 
- 任务提交逻辑: - 当前工作线程为 0 时创建非核心线程执行任务 
- 已有非核心线程且队列未满时,任务入队等待 
 
- 适用场景: - 极低频非关键任务(如日志清理),避免常驻线程占用资源 
- 突发流量后需快速释放资源的临时任务(如活动促销统计) 
 
 
- 潜在风险与限制 - 线程无法复用:每次任务可能触发新建线程,增加创建/销毁开销 
- 队列容量敏感 - 无界队列(如 - LinkedBlockingQueue)可能导致 OOM
- 有界队列需精确设置容量防止任务堆积 
 
- 响应延迟:首次任务需等待线程创建,影响实时性要求高的场景 
 
- 替代优化方案 - 动态核心线程:通过 - allowCoreThreadTimeOut(true)允许核心线程超时回收,保留复用能力
- 弹性线程池 - 根据 QPS 动态调整 - corePoolSize(如夜间降为 0,高峰期恢复)
- 结合监控告警自动扩容/缩容(如 Prometheus + 动态配置中心) 
 
 
- 实践建议 - 稳定性要求高的系统(如支付服务)避免核心线程数为 0,优先动态调整策略 
- 测试环境需验证线程创建速率和任务响应时间,防止生产环境性能瓶颈 
 
shutdown & shutdownNow
线程池中 shutdown (),shutdownNow() 这两个方法有什么作用?
- shutdown() 的作用 - 新任务处理:立即拒绝新任务提交,触发拒绝策略(如抛 - RejectedExecutionException)
- 现有任务处理:等待已提交任务(包括执行中和队列中的)全部完成 
- 状态变化:将线程池状态从 - RUNNING变为- SHUTDOWN,最终进入- TERMINATED
- 线程中断:仅中断空闲线程,不干扰执行中的任务 
 
- shutdownNow() 的作用 - 新任务处理:立即拒绝新任务提交。 
- 现有任务处理 - 尝试中断所有正在执行的任务(通过 - interrupt())
- 清空队列并返回未执行任务列表( - List<Runnable>)
 
- 状态变化:将状态从 - RUNNING改为- STOP,最终进入- TERMINATED
- 线程中断:强制中断所有工作线程,无论是否空闲 
 
- 关键差异对比 - 新任务处理:均拒绝新任务 
- 现有任务处理 - shutdown()等待任务完成
- shutdownNow()中断任务并丢弃未执行任务
 
- 线程中断范围 - shutdown()仅中断空闲线程
- shutdownNow()中断所有线程
 
- 返回值 - shutdown()无返回值
- shutdownNow()返回未执行任务列表
 
- 适用场景 - shutdown():优雅停机(如服务正常退出)
- shutdownNow():紧急终止(如死锁或资源耗尽)
 
 
- 使用建议 - 组合调用:先调用 - shutdown()等待任务完成,若超时未终止再调用- shutdownNow(),需捕获- InterruptedException并处理。
- 任务容错设计:在任务代码中检查中断状态,确保能响应中断请求 
 
- 注意事项 - 资源泄漏:未正确关闭线程池可能导致线程和资源(如数据库连接)无法释放 
- 队列影响 - 无界队列使用 - shutdown()可能导致 OOM
- shutdownNow()清空队列可缓解内存压力
 
 
线程任务撤回
提交给线程池中的任务可以被撤回吗?
- 通过 - Future对象取消任务- 提交任务后返回 - Future对象,调用- cancel(boolean mayInterruptIfRunning)可尝试取消任务
- 参数作用 - mayInterruptIfRunning = true:若任务已执行,触发线程中断(需任务代码响应中断)
- mayInterruptIfRunning = false:仅取消未开始执行的任务
 
- 返回值 - true:任务成功取消(未开始或已响应中断)
- false:任务已完成或无法取消
 
 
- 不同任务状态下的撤回结果 - 任务未开始执行: - cancel()可成功移除队列中的任务
- 任务正在执行:依赖任务代码的中断响应逻辑(如检查 - isInterrupted()或捕获- InterruptedException)
- 任务已完成:无法撤回, - cancel()返回- false
 
- 线程池实现的影响 - ThreadPoolExecutor的局限性- 无界队列(如 - LinkedBlockingQueue)中的任务无法直接撤回,需通过- Future.cancel()
- remove(Runnable task)方法可移除队列中未执行的任务,但需明确任务引用
 
- 定时任务( - ScheduledThreadPool)- ScheduledFuture.cancel()可终止周期性任务,已开始的单次任务仍需中断响应逻辑
 
 
- 实践建议 - 代码容错设计:任务逻辑中定期检查中断状态,确保能响应取消请求 
- 组合关闭策略 - 先调用 - shutdown()等待任务完成,超时后使用- shutdownNow()强制终止
 
- 避免无界队列:使用有界队列(如 - ArrayBlockingQueue)减少无法撤回任务的风险
 
使用场景
奇偶数
多线程打印奇偶数,怎么控制打印的顺序
- 基础同步锁方案 - 同步机制:使用 - synchronized保证原子操作,避免竞态条件
- 双重检查机制 - 外层 - while(num <= 100)控制整体循环
- 内层 - while(num%2 ==0)实现条件等待
 
- 线程协作 - wait()释放锁并暂停当前线程
- notify()唤醒等待线程
 
 
- 标志位优化方案 - 显式状态控制:通过 - isOddTurn布尔标志管理奇偶切换
- 性能优化 - 避免频繁计算奇偶性,提升执行效率 
- 便于扩展多线程交替逻辑(如三线程 ABC 交替) 
 
 
- 高级锁机制方案 - 精细控制:使用 - ReentrantLock替代- synchronized,提供更灵活的锁管理
- 条件队列 - 通过 - Condition对象(- oddCond和- evenCond)分别管理奇偶线程等待队列
- signal()精准唤醒目标线程。
 
 
- 实现要点总结 - 同步机制选择:简单场景用 - synchronized,复杂场景用- ReentrantLock
- 条件判断:必须使用 - while循环检查条件,避免虚假唤醒
- 数值递增:在同步代码块内完成修改,保证原子性 
- 终止条件:循环外设置退出检测(如 - num > 100),防止数值越界
- 异常处理:捕获 - InterruptedException并确保锁最终释放
 
- 调试与扩展 - 调试建议 - 通过 - Thread.setName()设置线程名称,便于日志追踪
- 使用 - jstack检测死锁,- VisualVM监控线程状态
 
- 扩展应用场景 - 多阶段交替:修改为三个线程交替打印(如 ABCABC 序列) 
- 动态调整上限:通过 - volatile变量实现运行时修改最大值
- 分布式扩展:结合消息队列(如 Kafka)实现跨进程顺序控制 
 
 
单例模型
单例模型既然已经用了 synchronized,为什么还要在加 volatile?
- 解决指令重排序问题 - 对象实例化操作 - instance = new Singleton()在 JVM 中分为三步- 分配内存空间 
- 调用构造函数初始化对象 
- 将对象引用赋值给变量 
 
- 无 - volatile时可能发生指令重排序(如顺序变为 ①→③→②),导致其他线程获取未初始化完成的实例
- volatile通过内存屏障禁止指令重排序,确保初始化顺序为 ①→②→③,避免「半初始化」对象
 
- 保证可见性 - 当首个线程完成实例化后, - volatile强制将最新值刷新到主内存,其他线程立即感知- instance非空,避免重复创建
- 仅用 - synchronized时,其他线程可能因本地缓存未更新而误判- instance为空
 
- 双重检查锁定的完整性 - 第一次判空:减少锁竞争,仅实例未初始化时进入同步块 
- 第二次判空:防止多个线程通过第一次检查后重复初始化 
- volatile配合:确保第二次判空时读取最新值,避免指令重排序或可见性问题导致判空失效
 
- 性能与安全的平衡 - synchronized保证原子性,但无法单独解决指令重排序和可见性问题
- volatile以较低性能代价(内存屏障)补充- synchronized的不足,使 DCL 单例高效且线程安全
 
线程等待
3 个线程并发执行,1 个线程等待这三个线程全部执行完在执行,怎么实现?
- 使用 CountDownLatch - 核心机制:创建初始值为 3 的计数器,三个线程执行完毕后调用 - countDown()减少计数
- 等待线程:通过 - await()阻塞至计数器归零后执行后续任务
 
- 线程 Join 方法 - 实现方式:在等待线程中依次调用三个并发线程的 - join(),使其阻塞直至所有目标线程结束
- 注意事项 - 需按顺序先启动并发线程再启动等待线程 
- 适用于简单场景,代码耦合度较高 
 
 
- CompletableFuture 组合 - 核心方法:通过 - CompletableFuture.allOf()等待所有异步任务完成
- 优势:支持异步任务组合,可链式调用后续操作(如 - thenRun())
 
- 线程池与 awaitTermination - 实现流程 - 将三个任务提交至线程池 
- 调用 - shutdown()关闭线程池后,使用- awaitTermination()等待任务完成
 
- 优势:适合批量任务管理,可设置超时时间防止无限等待 
 
- 信号量 (Semaphore) - 控制逻辑 - 初始化三个许可,等待线程需调用 - acquire(3)获取全部许可后执行
- 每个并发线程执行后调用 - release()释放许可
 
- 适用场景:支持动态调整等待线程数量,灵活性较高 
 
- 方案选择建议 - 轻量级场景:优先选择 - CountDownLatch或- CompletableFuture,代码简洁且控制精准
- 批量任务管理:推荐线程池的 - awaitTermination,便于扩展和资源管理
- 动态调整需求:使用 - Semaphore可灵活控制许可数量
- 异常处理:所有方案需注意线程泄漏和死锁风险,确保资源释放 
 
并发读写
假设两个线程并发读写同一个整型变量,初始值为零,每个线程加 50 次,结果可能是什么?
- 可能的取值范围 - 最低结果:0(极端情况下所有加操作被覆盖) 
- 最高结果:100(理想线程串行执行) 
- 实际常见值:50~100 之间波动,可能更小(如交替执行多次覆盖修改) 
 
- 原因分析 - 非原子操作: - n += 1包含读取、修改、写入三步,可能被线程切换打断- 示例:线程 A 读 - n=0→ 线程 B 读- n=0→ 均写入- n=1,导致两次操作仅生效一次
 
- 缓存可见性问题:多核 CPU 缓存未同步,线程可能读取旧值(如线程 A 修改后未刷回主存,线程 B 仍用 - n=0计算)
- 指令重排序:编译器或 CPU 调整指令顺序,延迟写入操作加剧数据不一致 
 
- 解决方案 - 同步锁 - 使用 - synchronized或- ReentrantLock保证临界区代码原子性
 
- 原子变量 - 用 - AtomicInteger替代普通整型变量,通过- incrementAndGet()的 CAS 机制保证原子性,性能优于锁
 
- 线程安全容器 - 如 - ConcurrentHashMap提供细粒度并发控制,但本场景更适用原子变量
 
 
高级
线程池种类
- FixedThreadPool (固定大小线程池) - 核心参数: - corePoolSize = maximumPoolSize,无界队列- LinkedBlockingQueue
- 特点:线程数固定,适用于任务量稳定场景(如 Web 服务器请求处理) 
- 风险:无界队列可能导致 OOM 
 
- CachedThreadPool (可缓存线程池) - 核心参数:核心线程数 0,最大线程数 - Integer.MAX_VALUE,同步队列- SynchronousQueue
- 特点:动态扩容线程,空闲线程 60 秒回收(适合短期异步任务) 
- 风险:线程过多可能耗尽资源 
 
- SingleThreadExecutor (单线程池) - 核心参数:核心线程数 1,无界队列 - LinkedBlockingQueue
- 特点:保证任务顺序执行(如日志处理、数据库操作) 
 
- ScheduledThreadPool (定时任务线程池) - 核心参数:固定核心线程数,延迟队列 - DelayedWorkQueue
- 特点:支持延时/周期性任务(如定时备份、心跳检测) 
 
- WorkStealingPool (工作窃取线程池) - 核心参数:基于 - ForkJoinPool,默认线程数等于 CPU 核数
- 特点:任务窃取机制,适合并行计算(如分治算法、图像处理) 
 
- 自定义线程池 - 核心参数:通过 - ThreadPoolExecutor定制核心线程数、队列类型及拒绝策略
- 适用场景:高并发系统灵活平衡资源与效率 
 
- 其他补充 - ForkJoinPool:专为分治任务设计(如递归计算、大规模数据处理) 
- 拒绝策略:支持 - AbortPolicy(抛异常)或- DiscardOldestPolicy(丢弃旧任务)等
 
线程池参数
- corePoolSize (核心线程数) - 线程池保持活跃的最小线程数,空闲时不会被销毁(除非设置 - allowCoreThreadTimeOut=true)
- 新任务提交时优先创建核心线程处理,直到达到阈值 
 
- maximumPoolSize (最大线程数) - 允许创建的最大线程总数(核心线程 + 非核心线程) 
- 当队列已满且线程数小于该值时,创建非核心线程 
 
- keepAliveTime (空闲线程存活时间) - 非核心线程空闲时的最大存活时间,超时后销毁 
- 与 - unit配合使用,默认不影响核心线程
 
- unit (时间单位) - 定义 - keepAliveTime的时间单位(如秒、毫秒)
 
- workQueue (任务队列) - 存储待执行任务的阻塞队列,影响任务调度逻辑 
- 常见类型: - LinkedBlockingQueue(无界队列,可能 OOM)
- ArrayBlockingQueue(有界队列,推荐使用)
- SynchronousQueue(直接传递任务)
 
 
- threadFactory (线程工厂) - 自定义线程创建方式(命名规则、优先级、守护线程属性) 
- 默认使用 - Executors.defaultThreadFactory()
 
- handler (拒绝策略) - 线程和队列均满时处理新任务的策略 
- 常用策略: - AbortPolicy(默认,抛异常)
- CallerRunsPolicy(提交线程直接执行)
- DiscardPolicy(静默丢弃)
- DiscardOldestPolicy(丢弃最旧任务)
 
 
- 注意事项 - 队列选择原则: - CPU 密集型任务推荐有界队列(如 - ArrayBlockingQueue)
- I/O 密集型任务可考虑无界队列(需防范 OOM) 
 
- 参数联动规则: - 仅当 - workQueue已满且线程数 <- maximumPoolSize时创建非核心线程
- 使用无界队列时 - maximumPoolSize无效
 
- 推荐实践:手动创建 - ThreadPoolExecutor控制队列边界,避免使用- Executors默认实现
 
线程池设计
- 任务类型决定核心线程数 - CPU 密集型任务(如加密计算): - corePoolSize = CPU 核数 + 1(如 8 核设为 9 线程)
- I/O 密集型任务(如网络请求): - corePoolSize ≈ 2 * CPU 核数(如 8 核设为 16)
- 混合型任务: - corePoolSize = Ncpu * Ucpu * (1 + W/C),其中- W为任务等待时间,- C为计算时间
 
- 队列选择与容量设计 - 无界队列( - LinkedBlockingQueue):适用短耗时任务,明确设置容量防止 OOM(如- LinkedBlockingQueue(2000))
- 有界队列( - ArrayBlockingQueue):容量计算为- (corePoolSize / 任务平均耗时) * 最大容忍响应时间
- 同步队列( - SynchronousQueue):需高实时性,搭配- maximumPoolSize设为较大值(如 50-100)
 
- 线程池扩容与收缩规则 - 最大线程数:通常设为核心数的 1.5-3 倍(如电商场景核心 85,最大 150) 
- 空闲线程回收: - keepAliveTime设为 30-60 秒,突发流量可延长至 2-5 分钟
- 动态调整:根据 QPS 监控自动扩容( - 新核心数 = 当前 QPS * 平均耗时 / 1000 * 0.8)
 
- 拒绝策略与容错机制 - CallerRunsPolicy:允许主线程降级处理(如日志记录) 
- DiscardOldestPolicy:新任务优先级高(如实时消息覆盖) 
- 自定义策略: - 持久化存储:任务存数据库/Redis 后续处理 
- 异步重试:延迟后提交或转移至备用线程池 
 
 
- 监控与调优实践 - 监控指标:活跃线程数、队列堆积量、任务平均耗时(推荐 Prometheus 实时监控) 
- 压测验证:某电商优化后 QPS 从 4200 提升至 8500,错误率从 15% 降至 0.2% 
- 弹性设计:容器水平扩容(如 K8s)与线程池参数调优结合应对突发流量 
 
- 避坑指南 - 避免 - corePoolSize = maximumPoolSize(丧失突发能力)
- 明确队列容量(禁用默认无界队列) 
- CPU 密集型任务勿用 - CallerRunsPolicy(可能阻塞主线程)
 
工作原理
- 核心组成与初始化 - 线程池管理器:负责创建、销毁线程及任务调度,维护线程池生命周期 
- 任务队列:存储待执行任务,常用阻塞队列(如 - LinkedBlockingQueue),队列满时触发拒绝策略
- 工作线程:核心线程常驻处理任务,非核心线程队列满时动态创建,空闲超时后销毁 
- 线程工厂与拒绝策略:自定义线程属性(如命名规则),定义队列满时的处理逻辑(如抛异常) 
 
- 任务处理流程 - 任务提交:通过 - submit()或- execute()提交- Runnable/- Callable任务
- 资源分配 - 线程数 < 核心线程数:直接创建新线程执行 
- 核心线程已满:任务入队等待 
- 队列满且线程数 < 最大线程数:创建救急线程处理 
- 队列和线程均满:触发拒绝策略(如 - AbortPolicy)
 
- 任务执行:工作线程从队列取任务执行,完成后等待新任务 
 
- 线程池状态管理 - 状态标识:通过 - ctl变量(高3位状态,低29位线程数)维护:- RUNNING:正常接收并执行任务 
- SHUTDOWN:不再接收新任务,处理存量任务 
- STOP:中断所有任务,丢弃未处理任务 
- TERMINATED:线程池完全终止 
 
- 状态转换: - shutdown()进入 SHUTDOWN,- shutdownNow()进入 STOP,完成后转为 TERMINATED
 
- 性能优化机制 - 线程复用:避免频繁创建/销毁,减少上下文切换(每次 1-10 微秒) 
- 动态伸缩:根据任务量调整救急线程数量,空闲超时后回收 
- 队列选择 - CPU 密集型:有界队列(如 - ArrayBlockingQueue)
- I/O 密集型:无界队列(如 - LinkedBlockingQueue)
 
- 拒绝策略适配:电商系统采用 - CallerRunsPolicy由提交线程执行任务
 
- 典型应用场景 - Web 服务器:Tomcat 用固定核心线程数(如200)处理 HTTP 请求 
- 异步任务处理:日志写入用缓存线程池( - CachedThreadPool)动态扩容
- 定时任务调度: - ScheduledThreadPool支持延迟/周期性任务(如数据同步)
- 数据库连接池:复用连接减少建立开销(类比线程池设计)。 
 
等待队列
为啥 Java 线程池核心线程满后,入队列等待,Tomcat 核心满后不入队列等待的原因
- 设计目标差异 - Java 线程池:面向 CPU 密集型任务,优先用队列缓冲任务减少线程创建开销。核心线程满后任务入队,队列满时才创建非核心线程。 
- Tomcat 线程池:针对 I/O 密集型场景优化,优先创建新线程降低延迟,核心满后直接扩容线程至最大值,队列仅作最后缓冲 
 
- 队列行为实现机制 - Java 默认队列:使用 - LinkedBlockingQueue,核心线程满后直接入队,无界队列可能导致 OOM
- Tomcat 自定义队列 - 重写 - offer()方法:当已提交任务数超过活跃线程数时返回- false触发创建新线程
- 仅在线程达最大值或队列满时才真正入队 
 
 
- 线程创建策略对比 - Java 线程池:严格遵循「核心线程→队列→非核心线程」流程,队列未满不扩容 
- Tomcat 线程池:核心线程满后,若提交任务数大于当前线程数,立即创建新线程直至最大值 
 
- 性能与资源权衡 - Java 队列优先:适合耗时长的 CPU 运算任务,减少线程切换损耗 
- Tomcat 线程优先:避免队列延迟,快速响应突发流量(如秒杀请求),默认限制队列长度( - acceptCount=100)防 OOM
 
- 参数配置体现差异 - Java 参数: - corePoolSize与- maximumPoolSize分离,依赖队列容量控制资源
- Tomcat 参数 - maxThreads限制线程总数,- acceptCount限制队列容量
- minSpareThreads预创建核心线程,减少首次请求延迟
 
 
内置线程池
为什么不推荐用内置线程池
- 资源耗尽风险 - 无界队列导致 OOM - FixedThreadPool和- SingleThreadExecutor使用- LinkedBlockingQueue,任务提交速度过快时队列无限堆积可能引发内存溢出。
- ScheduledThreadPool的延迟队列容量为- Integer.MAX_VALUE,周期性任务堆积存在 OOM 隐患
 
- 无界线程池问题 - CachedThreadPool允许创建- Integer.MAX_VALUE线程,高并发下可能耗尽 CPU/内存资源
 
 
- 配置灵活性与容错能力不足 - 参数固定化 - 内置线程池的队列类型、核心线程数等参数无法动态调整,难以适配突发流量或资源敏感型任务 
 
- 默认拒绝策略单一 - 默认使用 - AbortPolicy(抛异常),缺乏降级、重试等容错机制,可能引发服务雪崩
 
 
- 可维护性与监控缺陷 - 线程命名模糊 - 内置线程池生成的线程名称(如 - pool-1-thread-n)无业务含义,增加排查难度
 
- 缺乏监控支持 - 无法直接获取活跃线程数、队列堆积量等关键指标,需额外开发监控逻辑 
 
 
- 生产环境最佳实践 - 推荐替代方案 - 使用 - ThreadPoolExecutor手动配置:- 有界队列:如 - ArrayBlockingQueue控制内存风险
- 合理线程数:根据 CPU 核数和任务类型(CPU/I/O 密集型)设置 - corePoolSize和- maximumPoolSize
- 自定义拒绝策略:结合 - CallerRunsPolicy或降级逻辑(如任务存入外部队列)
 
 
- 线程工厂与命名 - 自定义 - ThreadFactory为线程添加业务标识,便于日志追踪
 
- 生命周期管理 - 通过 - shutdown()和- shutdownNow()组合实现优雅关闭,避免线程泄漏
 
 
 
             
           
             
                         
             
            
评论区