一、什么是线程池?
- 线程池是对线程管理的一种池化技术,可以对线程进行统一的分配、调优和监控。它会预先创建一些线程放入到线程池中,当我们需要使用线程执行任务时,只需要向线程池提交任务。线程池会为我们选择原有线程或创建新线程来执行任务。
- 线程池中总会保留核心线程数量的线程,即使核心线程处于空闲状态也不对被销毁,而是等待新的任务执行。所以使用线程池可以线程复用,避免频繁的创建线程和销毁线程带来的资源消耗。
- 我们也可以通过线程池的最大线程数来控制系统最大并发数量,来保证系统的平稳运行。
二、线程池的优点?
- 降低资源消耗:可重复利用线程。
- 提高响应速度:当有新的任务时,可以不需要等待线程的创建就可以执行。
- 提高线程的可管理性:线程属于稀缺资源,若无限制创建,会降低程序的稳定性。线程池可以通过最大线程数来限制线程数量。
三、线程池的参数详解
线程池有七大核心参数:
- 核心线程数:线程池中长期存活的线程数,即使线程空闲也不会被销毁。
- 最大线程数:线程池中允许的最大线程数量。当阻塞对列满了后就会在最大线程数范围内创建新的线程来处理。
- 空闲线程存活时间:核心线程数外的线程的空闲存活时间。
- 空闲线程存活时间单位:核心线程数外的线程的空闲存活时间单位。
- 阻塞队列:当任务数量大于核心线程数后会被存放到阻塞队列。
- 线程工厂:创建线程的工厂。
- 拒绝策略:当线程数量达到最大线程时候,还有新的任务到达时就会采用拒绝策略处理。
四、线程池的工作流程
假设线程是核心线程数为3,最大线程数为5,阻塞对列容量为7;
若某一短时间内有15个任务按任务1,任务2,任务3....的顺序提交到线程池,那么,任务1,任务2,任务3会被核心线程执行,任务4....任务10会进入到阻塞对列;当任务11,任务12被提交到线程池时,就会创建新的线程来执行任务11,任务12,当任务13....任务15被提交时就会被拒绝。如上图所示。
当线程任务执行完后,就会从阻塞对列中获取新的任务执行,如上图。
此时,当再有新的任务到达时,加入到阻塞队列尾部,若阻塞队列已满,则拒绝任务。如上图。
当阻塞队列中的任务被执行完毕,未有新的任务到达,线程池中线程数量大于核心线程数,存在空闲线程并且超过空闲线程存活时间,线程将被销毁。如上图所示。
五、线程池的几种状态
- RUNNING
- 线程池处于RUNNING状态时,能够接收新任务以及对已添加的任务进行处理。
- 线程池的初始状态为RUNNING。换句话说线程池一旦被创建,就处于RUNNING状态,且线程池中的任务数为0.
- SHUTDOWN
- 线程池处于SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
- 调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN
- STOP
- 线程池处于STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
- 调用线程池的shutdownNow()接口时,线程池由(RUNNING)或者(SHUTDOWN)->STOP
- TIDYING
- 当所有的任务已终止,ctl记录的任务数为0,线程池的状态会变为TIDYING状态;当线程池的状态变为TIDYING状态时,会调用钩子函数terminated(),该方法在ThreadPoolExecutor中是空的,若用户想在线程池变为TIDYING时进行相应的处理,就需要重载terminated()函数实现。
- 当线程池状态为SHUTDOWN时,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUTDOWN->TIDYING
- 当线程池为STOP时,线程池中执行的任务为空时,就会又STOP->TIDYING
- TERMINATED
- 线程池彻底终止,就会变成TERMINATED状态
- 线程池处于TIDYING状态时,调用terminated()就会由TIDYING->TERMINATED)
六、源码解析
-
execute()
我们通过注释可以发现,execute()大概分为3步。
- 如果正在运行的线程数量小于核心线程数,那么就尝试开启一个新的线程来执行任务。
- 如果任务被成功排队,我们仍需要二次校验,因为可能会存在进入此方法后线程池被执行shoutdown()。若检查失败,则需要回滚排队。
- 若果任务排队失败,则尝试开启一个新的线程来执行任务,若果开启失败,则为线程池已被关闭或已经饱和,则需要拒绝此任务。
addWorker()在execute()会被调用2次,第一次为当前工作线程数小于核心线程数时,第二个则是当前阻塞队列满时被调用。
addWorker()首先检查当前线程池是否满足创建新线程条件,即当前工作线程数小于核心线程数及线程池的状态处于可接收新指令状态。
创建线程则是创建Worker对象。Worker是ThreadPoolExecutor的内部类并且实现了Runnable接口。它有两个成员属性。thread和firstTask,thread是用来存储线程工厂创建的线程,firstTask则是新接收的任务。使用线程工厂创建线程时将当前的Worker做为runnable参数传递。所有启动线程就是执行Worker的run()。而run()又是调用runWorker()。
runWorker()则是执行Worker对象的firstTask。即完成了任务调用。runWorker也会不断的从阻塞队列中获取任务执行。