并发编程----4.java并发包中线程池的原理研究
java并发包中线程池ThreadPoolExecutor的原理研究
线程池的优点:线程的复用,减少线程创建和销毁带来的消耗;提供了一种资源限制和线程管理的手段,比如限制线程的个数和动态新增线程等。线程池继承 AbstractExecutorService
介绍
-
ctl:原子变量,用来记录线程中的线程池状态和线程个数,类似于ReentrantReadWriteLock使用一个变量state保存两种信息
- 高三位表示线程池的状态(Integer 32位)
- 29位记录线程池的个数
-
线程池的状态
- shutdown:拒绝新的任务但是处理阻塞队列中的任务
- stop:拒绝新任务并且抛弃阻塞队列中的任务,同时中断正在处理的任务
- tidying:所有的任务执行完了之后包括阻塞队列中的线程,将调用terminated
- terminated:终止状态
- running:接受任务并且处理阻塞队列中的任务
-
线程状态转化
- running–>shutdown
- runining/shutdown–>stop
- shutdown–>tidying
- stop–>tidying
- tidying–>terminated
-
线程池参数
- corePoolSize: 线程池核心线程数
- workQueue: 用于保存等待执行任务的阻塞队列,例如:数组有界ArrayBlockingQueue、基于链表的LinkedBlockingQueue、最多一个元素的SynchronousQueue,优先队列PriorityBlockingQueue
- maximunPoolSize:线程池最大线程数量
- ThreadFactory:创建线程工厂
- RejectedExecutionHandler:饱和策略,当队列满且线程数达到MaximumPoolSize后采取的策略,比如抛出异常,AbortPolicy;CallerRunsPolicy调用者所在的线程运行任务。DiscardOldestPolicy丢弃一个任务执行当前任务。DiscardPolicy默默抛弃不抛出异常
- keeyAliveTime:存活时间,当前线程比核心线程多,并且处于闲置状态,则这些闲置的线程能存活最大时间
-
线程池类型
- newFixedThreadPool
- 创建一个核心数和最大线程数都为n的线程池,并且阻塞队列为Integer.MAX_VALUE。keekAliveTime=0说明线程个数比核心线程多并且空闲则回收
- newSingleThreadExecutor
- 创建一个核心数和最大线程数都为1的线程池,并且阻塞队列为Integer.MAX_VALUE。keekAliveTime=0说明线程个数比核心线程多并且空闲则回收
- newCacheThreadPool
- 按需创建线程的线程池,初始线程个数为0.最多线程数为Integer.MAX_VALUE,并且阻塞队列为同步队列,keepAliveTime=60说明只要任务只要在60s内就被回收。当加入同步任务就会马上被执行,同步队列中最多只有一个任务
- 按需创建线程的线程池,初始线程个数为0.最多线程数为Integer.MAX_VALUE,并且阻塞队列为同步队列,keepAliveTime=60说明只要任务只要在60s内就被回收。当加入同步任务就会马上被执行,同步队列中最多只有一个任务
- newFixedThreadPool
-
mainLock为独占锁,用来控制新增worker线程操作原子性,termination是该锁对应的条件队列,在线程调用个awaitTermination时存放阻塞的线程。
-
Work继承AQS和Runnable接口,是具体承载任务的对象,简单实现了不可重入锁,其中state=0为锁未被获取状态,state=1表示获取状态-1为默认。firstTask执行的第一个任务。
-
DefaultThreadFactory是线程工厂,newThread 方法是对线程一个修饰。其中poolNumber是个静态原子变量,用来统计线程工厂个数。threadNumber统计线程工厂创建了多少线程。
-
源码分析
- public void execute(Runnable command)提交任务
- 当当前线程数个数小于corePoolSize,会向workers里面新增核心线程执行该任务
- 当大于或者等于corePoolSize放入任务队列(workQueue)中,如果当前线程池状态不是running的话就抛弃新任务
- 添加任务成功,则对线程进行第二次的状态校验,这是因为任务放入任务队列后,执行代码前任务可能已经变化,这里进行校验,如果是非running状态就从队列里面移除任务,并且执行拒接策略。如果二次校验通过,重新判断线程池里面是否有线程,如果没有新增一个线程。
- 如果任务失败,说明任务队列已满,尝试开始新的线程,如果当前线程个数>maximumPoolSize则执行拒绝策略。
- 添加任务
- 检查队列是否只在必要的时候为空(返回情况:stop,tidying,terminated;shutdown并且队列中到一个一个任务;shutdown并且队列为空)
- cas增加线程个数(超出最大线程数返回),成功返回,失败查看是否是状态改变是就返回,不是就重新cas
- 创建worker,使用mainLock实现wokers同步,
- 检查状态是否被shutdown了,是就返回不是添加任务,
- 成功启动任务。
- 工作线程worker的执行
- 获取任务,如果任务为null则执行清理工作,如果不为空,获取该任务的锁,执行相关任务,统计worker完成了多少任务,并且释放锁(加锁的目的是为了防止其他线程调用了shutdown,阻止当前任务的执行)
- 清理操作,统计完成任务数量,从工作集中删除当前任务,尝试设置为terminated状态。如果当前线程<核心个数增加。
- shutdown操作
- mainLock锁住
- 权限检查,调用的线程师傅有关闭的权限,没有抛出异常
- 设置为shutdown,是直接返回,不是设置为shutdown
- 设置中断标志、尝试设置为terminated,设置所有空闲线程中断标志。尝试获取worker自己的锁,成功则设置中断标志,目的是为了阻塞要从任务队列中获取任务的空闲队列。方法:使用cas 设置当前线程池的状态为tidying,设置成功就执行terminated,设置为terminated,激活所有因为调用条件变量await系列方法阻塞的所有线程。
- 解锁
- shutdownNow操作
- 不接受新的任务也不执行任务队列中的任务,正在执行的任务会立刻被中断,该方法会返回
- awaitTermination操作
- 当前线程不会被阻塞,知道线程状态变为terminated才返回,或则等待时间超时才返回。
- public void execute(Runnable command)提交任务
java并发包中线程池ScheduledThreadPoolExecutor的原理研究
指定一个延迟时间后或者定时进行任务调度执行的线程池。ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService,线程池队列是DelayedWorkQueue。
- 介绍
- state 状态:ScheduledFutureTask是具有返回任务的继承自FutureTask。FutureTask内部有一个state用来表示任务状态
- 初始状态:new;执行中状态:completing : 执行中状态;normal:正常运行结束状态;exceptional:运行中异常;cancelled:任务取消;interrupting: 正在中断;interrupted:已经中断。
- 状态装换:new–>completing -->normal;new–>completing–>exceptional;new–>canceled;new–>interrupting–>interrupted
- period 任务类型:
- period=0:任务是一次性的
- period<0:任务是固定延迟时间可重复执行任务
- period>0:任务是固定频率的定时可重复执行任务
- state 状态:ScheduledFutureTask是具有返回任务的继承自FutureTask。FutureTask内部有一个state用来表示任务状态