线程池
线程池内部结构及工作原理
拒绝策略4种
- AbordPolicy 抛出异常,默认的,无法接收任务,交给业务处理用的最多
- CallerRunsPolicy
- DiscardOldestPolicy 丢弃队列中最老的任务,再次尝试提交
- DiscardPolicy 直接丢弃
线程池状态
RUUNING
SHUTDOWN
调用shutdown,工作队列为空,线程为空 tiding。消费完后退出,提交会拒绝,获取独占锁,获取到代表空闲,STOP
调用shutdownnow
线程为空 转换为tiding
,不检查独占锁,直接中断,空闲线程不管队列任务。检查是否为最后一个转换状态TIDYING
调用钩子方法 terminated方法TERMINATED
ThreadPoolExecutor
maximumPoolSize
最大工作线程数,获取不到任务时超过keepAliveTime
回收
corePoolSize
核心工作线程数
- 当前线程池线程数量 <
corePoolSize
创建worker
- 当前线程池线程数量 >
coolPoolSize
放入队列 - 队列满了?工作线程数是否 >
maximumPoolSize
AtomicInteger ctl
- 高3位,当前线程池状态
- 除去高3位的地位表示当前线程池中所拥有的的线程数量
- RUNNING < SHUTDOWN <STOP <TIDYING< TERMINATED
- 线程池中全局锁 ReentrantLock mainLock ,增加减少线程池工作线程数量,改变线程池运行状态
- allowCoreThreadTimeOut true 核心线程空间超过keepAlive也会回收,false,coresize意外的线程超过keepAlive 空闲会被回收
Worker
Worker extends AbstractQueuedSynchronizer implements Runnable
采用独占模式,worker工作中时,会加锁修改state值,外部线程可以通过这个lock状态知道worker是空闲还是忙碌
execute(Runnable command) 提交任务到线程池,创建工作线程
- ctl.get 判断当前线程数小于coresize 成立,
addWorkder
- 采用核心线程数创建worker,成功返回
- 失败重新获取最新ctl值,存在并发现象,线程池状态发生改变,一般情况下只有RUNNING状态是addWorker会成功。SHUTDOWN状态下会成功的前提条件是 firstTask == null 而且当前 quene != null
- 判断线程池工作状态 && 添加任务到队列 workQueue.offer(command)
- 前置条件,corePoolSize已达到,addWorker失败(多线程工作,coresize满了)
- 当前线程池处于RUUNING状态,并且将任务放入队列中
- 再次获取ctl
- task提交队列后,线程池状态改变,需要删除任务
- 成立,非RUNNING,任务出队成功
- 判断当前工作线程数 == 0,担保,保证RUNNING状态下有最少一个线程在工作
- 当前线程池处于RUUNING状态,并且将任务放入队列中
- 前置条件,offer 失败,线程状态非running
- 尝试走maximumPoolSize
- 非RUNNING状态addWorker并且command != null ,一定失败
- 前置条件,corePoolSize已达到,addWorker失败(多线程工作,coresize满了)
addWorker(Runnable firstTask,boolean core)
-
retry 自旋:判断当前线程池状态是否允许创建工作线程
- 先获取ctl,根据clt查看当前线程池工作状态
- 判断当前线程池状态是否允许添加工作线程
- 内部自旋,获取令牌,cas ctl
- 获取工作线程数,并判断当前工作线程数是否大于core | maximum
- cas 工作线程数 +1
- 成立,申请到一块令牌,跳出循环retry
- 失败,说明其他线程已经修改过ctl的值了
- 重新获取ctl值,判断当前线程池状态是否发生改变,返回到retry循环
-
初始化 两个boolean 变量,
workerstarted
工作线程是否启动,workeradd
工作线程是否添加
-
创建worker,判断当前woker.thread != null
- 获取全局所的赋值给mainLock,持有全局锁,会阻塞,线程池内部操作必须获取锁
- 判断当前线程池状态,添加工作线程 workers.add(w),判断是否成功添加
- 解锁,判断workerAdded,启动线程
- 添加worker失败做清理工作,释放令牌,workers.remove
-
失败的几种情况
- 线程池状态rs > SHUTDOWN (STOP/TIDYING/TERMINATION)
- rs == SHUTDOWN 但是队列中已经没有任务了 或者 当前状态是SHUTDOWN且队列未空,但是firstTask不为null
- 当前线程池已经达到指定指标(coprePoolSize 或者 maximumPoolSIze
- threadFactory 创建的线程是null
runWorker(Worker w)
-
调用 w.unlock ;这里为什么先调用unlock? 新建worker时候为-1,就是为了初始化worker state == 0 和 exclusiveOwnerThread ==null
-
自旋:自旋条件,task != null ,任务队列中获取任务成功(会阻塞)
- 为什么要设置独占锁呢?shutdown时会判断当前worker状态,根据独占锁是否空闲来判断当前worker是否正在工作。
- 根据线程池状态判断当前线程是否需要interrupt
- task.run
- task设置为空更新worker完成任务数量,释放锁
-
completedAbruptly = false; etTask()方法返回null时,说明当前线程应该执行退出逻辑了。
-
getTask()方法返回null时,processWorkerExit 说明当前线程应该执行退出逻辑了。
getTask()
- 自旋:
-
获取最新ctl赋值给c
-
获取当前线程池运行状态赋值给 rs
-
1.线程池是RUNNING状态
2.线程池是SHUTDOWN状态 但是队列还未空,此时可以创建线程。 -
获取线程池中工作线程数量赋值给wc
-
判断线程是否需要被回收
- cas ctl -1 成功返回null
- cas 失败
- 其他线程先你一步退出了
- 线程池状态发生变化
-
判断已哪种方式获取队列中任务,超时方式,还是非超时方式,runnable为空证明超时赋值timedOut
-
总结:什么情况下会返回null
- rs >= STOP 成立说明:当前的状态最低也是STOP状态,一定要返回null了
- 前置条件 状态是 SHUTDOWN ,workQueue.isEmpty()
- 线程池中的线程数量 超过 最大限制时,会有一部分线程返回Null
- 线程池中的线程数超过corePoolSize时,会有一部分线程 超时后,返回null。
processWorkerExit(Worker w, boolean completedAbruptly)
- 是否异常退出 ctl -1
- 获取全局锁的引用加锁
- 将当前worker完成的任务数+= 全局completedTaskCount, workers.remove,并释放锁
- 获取最新ctl,判断当前线程状态是 running,shutdown状态
- 正常的退出的线程
- 退出前判断任务队列是否为空,保证线程池中有工作线程
- 异常退出线程必须创建一个空任务线程
- 正常的退出的线程
shutdown()
- 获取线程池全局锁
- 设置线程池装填为shutdown
- 中断空闲线程
- 获取全局锁
- 遍历workers,获取w.thread
- 判断thread.isInterrupted && 获取到work的锁,说明当前worker处于空闲状态
- 设置线程状态为,interrupt,释放锁
- 释放全局锁
shutdownNow()
- 强制设置线程池状态
- 中断线程池中所有线程
- 获取锁,遍历workers,调用thread.interruptStarted
- 释放全局锁
- 导出未处理的task