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,为的就是实现不可重入的特性去反应线程现在的执行状态。
- 增加
- 执行