线程池概述
由来
为减少频繁创建和销毁线程的开销,引入池
的概念:提前定制一定量的资源来进行资源复用
作用
1、控制线程数量。2、工作线程可被复用。3、线程池参数调优,高效利用资源。
核心类介绍
ExecutorService
线程池上层接口
ThreadPoolExecutor
ExecutorService的默认实现,供Executors工厂类创建线程池的底层使用
构造参数含义:
- corePoolSize:池中所保存的线程数,包括空闲线程。
- maximumPoolSize:池中允许的最大线程数。
- keepAliveTime:当线程数大于核心时,此为终止多余的空闲线程前允许其等待新任务的最长时间。
- unit:keepAliveTime参数的时间单位。
- blockingQueue:执行前用于保存任务的队列。此队列仅保持由execute方法提交的Runnable任务。
- threadFactory:创建新线程时使用的工厂。
- rejectedExecutionHandler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
blockingQueue
工作原理
如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队。
如果运行的线程等于或多于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
任务的三种排队策略
1、直接提交。
工作队列的默认选项是SynchronousQueue,在添加任务后必须等待其他线程取走后才能继续添加。如果不存在可用于立即运行任务的线程,且线程数小于池子最大数,会构造一个新的线程。这一点很重要,核心线程打满时,在同步队列里的任务会立即触发额外线程来处理任务,由此保证提交到线程池的任务立即顺序执行,避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes以避免拒绝新提交的任务。
2、无界队列。
使用无界队列(例如,不具有预定义容量的LinkedBlockingQueue)将导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在Web服务器中。这种排队可用于处理瞬态突发请求。
3、有界队列。
当使用有限的maximumPoolSizes时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
4、延时队列。
定时线程池使用延时队列DelayedWorkQueue实现定时执行任务。延时队列基于最小堆构造,最近要执行的延时任务放在堆顶,内部有一个leader线程指向等待最近一个要执行任务的线程
offer方法提交延时任务:
- 任务入最小堆,如果任务在堆顶,说明是最近一个要执行的任务,则唤醒一个等待在延时队列的线程,让它作为leader线程,再执行awaitNanos方法等待最近的延时任务
take方法获取延时任务:
- 堆顶无任务,则线程await,等待在延时队列
- leader线程不为空说明有任务在等待最近一次延时任务,则线程await,等待在延时队列
- 否则,当前线程作为leader线程,等待堆顶的最近一次延时任务,即执行awaitNanos方法延时等待
- 期间如果有更紧急的延时任务提交,则leader线程会置空,并唤醒一个等待在延时队列的线程,作为新的leader线程等待在最近的延时任务上
- 获取到延时任务后,leader线程置空,并唤醒一个等待在延时队列的线程,作为leader线程继续等待下一个延时任务
RejectedExecutionHandler
jdk提供了四种任务拒绝策略RejectedExecutionHandler的实现
CallerRunsPolicy
线程池未关闭时,在线程池运行的主线程中执行被拒绝任务。
这种策略提供了一个简单的反馈控制机制,缺点就是可能会阻塞主线程
AbortPolicy
拒绝任务时会抛出运行时RejectedExecutionException到线程池运行的主线程中它是线程池默认的拒绝策略
DiscardPolicy
直接丢弃任务,和AbortPolicy的唯一区别就是AbortPolicy抛出异常,DiscardPolicy不做任何处理
DiscardOldestPolicy
线程池未关闭时,丢弃阻塞队列中最老的(头部)任务,然后再将该任务提交到线程池中执行
Executors
创建线程池的一个工厂类
1.newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
core=1,max=1,keepAliveTime=0,LinkedBlockingQueue
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到达到线程池的最大线程数。一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
core=n,max=n,keepAliveTime=0,LinkedBlockingQueue
3.newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
core=0,max=MAX_VALUE,keepAliveTime=60s,SynchronousQueue
4.newScheduledThreadPool
创建一个定时线程池。此线程池支持定时以及周期性执行任务的需求
core=n,max=MAX_VALUE,keepAliveTime=10ms,DelayedWorkQueue
线程池注意事项
1、并发可能产生的死锁问题
2、线程泄漏:使用局部线程池造成线程泄漏
- 简单理解,线程池里的线程执行完任务后并不会stop,而是在while循环里不断轮询阻塞队列(请求队列)拿任务
- ThreadPoolExecutor -> Worker -> Thread,由于存在这样的引用关系,并且Thread作为GC Root,所以无法被回收
线程池工作模型
1、线程池管理器
- 核心是管理工作线程
- 构造线程池时,初始化一些工作线程,添加到工作线程队列并启动
- 接收外部任务,提交到请求队列。请求队列拒绝时,比较工作线程队列的线程数和核心线程数、最大线程数的关系,决定是创建新的工作线程还是拒绝任务
- 关闭线程池时,停止所有工作线程
2、请求队列
- 核心是服务于工作线程取任务
- 提交到线程池的任务,同步添加到请求队列
- 添加后notify所有等待在请求队列的工作线程取任务
- 提供接口给工作线程,同步从请求队列取任务
- 请求队列为空时,wait在请求队列
- 请求队列满时,调用拒绝策略
- 提交到线程池的任务,同步添加到请求队列
3、工作线程
- 核心是从请求队列里取任务执行
- 轮询请求队列,获取并执行任务
4、工作线程队列
- 核心是维护创建的工作线程
- 负责工作线程的入队和出队