线程池简析

线程的本质

线程池简析


Java 线程池

  • newCachedThreadPool
    • 创建一个可缓存的无界线程池,该方法无参数。
    • 只有非核心线程,线程数量不定的线程池,为无限大,最大线程数为Integer.MAX_VALUE
    • keepAliveTime为60秒
    • 使用SynchronousQueue作为工作队列。这种队列不存储元素,任务提交后必须有空闲线程立即接收,否则会创建新线程(如果未达到maximumPoolSize)。
    • 如果任务提交速度过快,会创建大量线程(理论上可达Integer.MAX_VALUE个),可能耗尽系统资源,导致OOM以及频繁的上下文切换。
  • newFixedThreadPool
    • 创建一个定长线程池
    • 使用无界的LinkedBlockingQueue作为工作队列
    • 无超时机制
    • 当任务提交速度远大于处理速度时,队列会持续增长,可能导致内存溢出(OOM)
  • newSingleThreadExecutor
    • 创建只有一个线程的线程池,该方法无参数
    • 所有任务都保存无界的LinkedBlockingQueue中,等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行。
    • 无界队列可能导致OOM
  • newScheduledThreadPool/newSingleThreadScheduledExecutor
    • 创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService比Timer更安全,功能更强大
    • 可指定线程池的核心线程个数
    • 非核心数量无限制,maximumPoolSize为Integer.MAX_VALUE。但由于队列是DelayedWorkQueue,通常不会无限增长线程,除非有大量同时到期的任务且处理不过来
    • 非核心线程闲置时立即被回收
    • 任务队列是DelayedWorkQueue (一种特殊的PriorityQueue)

线程池参数

corePoolSize:

  • 如果当前运行的线程数小于corePoolSize,则创建新线程(即使有空闲的核心线程)来执行任务。
  • 除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。

maximumPoolSize:

  • 如果workQueue.offer()失败(队列已满),创建小于maximumPoolSize的线程
  • 如果是有界队列,当队列满时,仍然有任务进来,此时线程池会创建小于最大线程数的线程。
  • 如果是无界队列,会一直保持核心线程数,多余的任务会一直往队列中加入。

keepAliveTime:

  • 非核心线程空闲存活时间
  • allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程
  • 也可以使用setKeepAliveTime()动态地更改参数

workQueue:

  • 阻塞队列,核心线程数满时,新加入的任务,会先添加到阻塞队列中等待线程获取任务并执行。
  • BlockingQueue的插入/移除/检查这些方法,对于不能立即满足但可能在将来某一时刻可以满足的操作,共有4种不同的处理方式
    • 第一种是抛出一个异常
    • 第二种是返回一个特殊值(null 或 false,具体取决于操作)
    • 第三种是在操作可以成功前,无限期地阻塞当前线程
    • 第四种是在放弃前只在给定的最大时间限制内阻塞。

threadFactory:

  • 创建新线程的工厂
  • 可以为线程池中的线程设置更有意义的名称、设置守护线程状态、设置线程优先级、指定UncaughtExceptionHandler等
  • Executors.defaultThreadFactory()是默认实现

handler:

  • 核心线程和非核心线程都已用尽,且队列也满了,执行RejectedExecutionHandler所定义的拒绝策略
  • 线程饱和策略,默认策略为AbortPolicy,直接在当前线程抛出异常。
  • 实现RejectedExecutionHandler接口自定义拒绝策略,可以做一些打印观察日志的操作,告警、兜底的相关处理等。
  • ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略)
  • ThreadPoolExecutor.CallerRunsPolicy:提交任务的线程,直接执行任务。此策略提供简单的反馈控制机制,变相的背压机制,能够减缓新任务的提交速度。
  • ThreadPoolExecutor.DiscardPolicy:直接丢弃被拒绝的任务,不做任何通知
  • ThreadPoolExecutor.DiscardOldestPolicy:位于工作队列头部的任务(最旧的任务)将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。

线程池类

  • Executor
    • 顶层接口
    • 将任务提交和任务执行进行解耦
    • 用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
  • ExecutorService
    • 继承Executor的接口
    • 提供了更完善的生命周期管理能力,通过Future对象提供任务取消、状态查询、结果获取能力实现了任务监控。
  • AbstractExecutorService
    • 抽象类
    • 将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可
  • ThreadPoolExecutor
    • 核心实现
    • 提供了高度可配置的线程池,允许我们精细控制线程池的各种行为
    • 一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
  • ScheduledThreadPoolExecutor
    • ScheduledExecutorService接口的实现类,继承ThreadPoolExecutor
    • 专门用于处理定时和周期任务
  • Executors
    • 一个静态工厂模式的工具类,提供了一系列静态方法来创建各种常见配置的线程池,简化了创建线程池的使用但是会带来一些问题,很多开发规范里都不建议大家直接使用。
    • 生产环境中建议谨慎使用或直接使用ThreadPoolExecutor构造函数自定义。

线程池原理

任务管理:

  • 任务调度
  • 任务缓冲。不同的队列可以实现不一样的任务存取策略。
  • 任务申请
  • 任务拒绝。JDK提供的四种已有拒绝策略

线程管理:

  • 创建
    • Worker这个工作线程,实现了Runnable接口。并持有一个线程thread,一个初始化的任务firstTask。
    • thread是在调用构造方法时通过ThreadFactory来创建的线程
    • firstTask。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
  • 回收
    • 线程长时间不运行的时候进行回收。线程池使用一张Hash表去持有线程的引用,这样可以通过添加引用、移除引用这样的操作来控制线程的生命周期。将其引用消除可被JVM自动的回收。
    • Worker是通过继承AQS,使用AQS来实现独占锁这个功能。没有使用可重入锁ReentrantLock,而是使用AQS,为的就是实现不可重入的特性去反应线程现在的执行状态。
  • 增加
  • 执行
相关推荐
阿冰冰呀6 天前
互联网大厂Java求职面试实录:谢飞机的“水货”之路
java·mybatis·dubbo·springboot·线程池·多线程·hashmap
YYYing.7 天前
【C++项目之高并发内存池 (二)】整体框架设计与ThreadCache的初步实现
笔记·高并发·线程池·c/c++
__土块__10 天前
AI 任务调度器频繁超时:一次从线程争用到执行隔离的工程复盘
线程池·可观测性·任务调度·系统稳定性·生产故障·ai工程·执行隔离
小辉同志10 天前
Epoll+线程池
开发语言·c++·c·线程池·epoll
Zzzzmo_11 天前
【JavaEE】多线程04—线程池/定时器
java·线程池·定时器·javaee
W230357657313 天前
【改进版】C++ 固定线程池实现:基于调用者运行的拒绝策略优化
开发语言·c++·线程池
洛水水15 天前
# 线程池详解:从原理到实现
c++·线程池
__土块__16 天前
Java 大厂一面模拟:从线程池拒绝策略到分布式锁选型的连环压问
线程池·分布式锁·redisson·java面试·拒绝策略·大厂一面·kafka幂等
UrSpecial18 天前
从零实现C++轻量线程池
c++·线程池
__土块__18 天前
Java 大厂一面模拟:从线程本地存储到分库分表路由的连环拷问
kafka·线程池·分库分表·java面试·threadlocal·缓存一致性·大厂一面