为什么使用线程池?
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。假设完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高性能。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
ThreadPoolExecutor 的类关系
Executor
Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
ExecutorService
ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,可以说是真正的线程池接口;
AbstractExecutorService
AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法;
ThreadPoolExecutor
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
ScheduledExecutorService
ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"功能ExecutorService;
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,实现ScheduledExecutorService接口,可以在给定的时间延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
线程池参数含义
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize 核心线程数
线程池中的核心线程数,当提交一个任务时,线程池创建一个新的线程执行任务,直到当前线程等于corePoolSize;
如果当前线程数等于corePoolSize,继续添加任务会被保存到阻塞队列中,等待被执行;
如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize 最大线程数
线程池允许的最大线程数量,如果阻塞队列满了。继续提交任务,会创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。
keepAliveTime 存活时间
线程空闲时的存活时间,当前线程没有执行任务时,继续存活的时间。默认情况下,该参数只有在线程大于corePoolSize时才有用。
unit 时间单位
keepAliveTime的时间单位
workQueue 阻塞队列
workQueue必须是BlockingQueue阻塞队列,当线程池中的线程数超过corePoolSize时,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。
一般来说,我们应该尽量使用有界队列,因为使用无界队列作为工作队列会对线程池带来如下影响。
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
- 由于1,使用无界队列时maximumPoolSize将是一个无效参数。
- 由于1和2,使用无界队列时keepAliveTime将是一个无效参数。
- 更重要的,使用无界queue可能会耗尽系统资源,有界队列则有助于防止资源耗尽,不过即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。
threadFactory 线程工厂
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名,当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程。
Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”。
handler 拒绝策略
线程池的饱和策略,当阻塞队列满了,且没有空闲的核心线程,如果继续提交任务,进行处理该任务策略,线程池提供了4种策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
- DiscardPolicy:直接丢弃任务;
线程池工作流程
- 如果当前运行的线程数少于corePoolSize,创建新的线程来执行任务(注意,执行这一步骤需要获取全局锁)。
- 如果运行的线程等于或者多于corePoolSize,则将任务加入BlockingQueue。
- 如果任务继续增加,无法将任务加入BlockingQueue(阻塞队列已满),则创建新的线程来处理任务。
- 如果创建新线程将使当前运行的线程数超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
创建线程池的方式
JDK8 提供了五种创建线程池的方法
newFixedThreadPool
创建一个固定长度线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
newWorkStealingPool
(JDK8新增)会根据所需的并发数来动态创建和关闭线程。能够合理的使用CPU进行对任务进行并发操作,所以适合使用在很耗时的任务。
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
}
newCachedThreadPool
创建一个可缓存的线程池,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
newSingleThreadExecutor
创建一个单线程的线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
使用线程池
execute
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
public void execute(Runnable command)
//使用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 10,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.DiscardPolicy());
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程 1");
});
submit
方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过Future的get方法来获取返回值,get方法会阻塞当前线程直到任务完成。
而使用get(long timeout, TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
/**
* 假设这个方法返回Future对象时f,f.get的返回值就是传给submit方法的参数result,
* result相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。
*/
public <T> Future<T> submit(Runnable task, T result)
/**
* 方法参数是一个Callable接口,只有一个call方法,并且这个方法是有返回值的,
* 可以通过get方法来获取任务执行结果。
*/
public <T> Future<T> submit(Callable<T> task)
/**
* 这个方法参数Runnable的run方法是没有返回值的,
* 所以这个方法返回的Future仅可以用来断言任务已经结束,类似于Thread.join();
*/
public Future<?> submit(Runnable task)
//使用
Callable<String> callable= new CallableImpl();
Future<String> future = threadPool.submit(callable);
//获取返回值,输出到控制台
System.out.println(future.get());
关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。
它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别。
shutdownNow
首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的集合。
shutdown
只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
如何判断关闭线程池?
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed方法会返回 true。
至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown() 方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow()方法。
线程池状态
线程有状态,线程池也有它的运行状态,这些状态提供了主生命周期控制,伴随着线程池的运行,由内部来维护,从源码中我们可以发现线程池共有5个状态
状态 | 该状态下可执行的操作 |
---|---|
RUNNING | 可以接收新的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 不能接收新的任务,但是却能继续处理阻塞队列中的任务 |
STOP | 不接收新任务,也不能处理队列任务,并且中断正在进行的任务 |
TIDYING | 所有任务都已经终止,workercount(有效线程数)为 0,线程转向 TIDYING 状态将会运行 terminated() 钩子方法 |
TERMINATED | terminated() 方法调用完成后变为此状态 |
阻塞队列BlockingQueue
队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
什么是阻塞队列
支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变为非空。
常用阻塞队列
有限队列就是长度有限,满了以后生产者会阻塞。
无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,当然空间限制来源于系统资源的限制,如果处理不及时,导致队列越来越大越来越大,超出一定的限制致使内存超限,操作系统或者JVM帮你解决烦恼,直接把你 OOM kill 省事了。
无界也会阻塞?
因为阻塞不仅仅体现在生产者放入元素时会阻塞,消费者拿取元素时,如果没有元素,同样也会阻塞。
以下队列都实现了BlockingQueue接口,也都是线程安全的。
有界队列
ArrayBlockingQueue
使用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证线程公平的访问队列。
公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。
非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置
LinkedBlockingQueue
由链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出(FIFO)的原则对元素进行排序。
Array实现和Linked实现的区别
- 队列中锁的实现不同
ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;
LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock - 在生产或消费时操作不同
ArrayBlockingQueue实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;
LinkedBlockingQueue实现的队列中在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会影响性能。 - 队列大小初始化方式不同
ArrayBlockingQueue实现的队列中必须指定队列的大小;
LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE。
LinkedBlockingDeque
由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。
多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst,不知道是不是JDK的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在“工作窃取”模式中。
SynchronousQueue
是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。队列本身并不存储任何元素,非常适合传递性场景。
无界队列
PriorityBlockingQueue
支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
DelayQueue
支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。
缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
LinkedTransferQueue
由链表结构组成的无界阻塞队列,增加了tryTransfer和transfer方法。
- transfer方法
如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。 - tryTransfer方法
tryTransfer方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回,而transfer方法是必须等到消费者消费了才返回。
线程池源码分析
线程池内部变量
线程池内部所需要的方法和属性解释
/**
* 原子类,记录当前线程池状态(runState)和 当前工作线程数(workerCount)
* ctlOf()返回值为RUNNING;
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//高3位表示线程状态
private static final int COUNT_BITS = Integer.SIZE - 3;
//最大线程数,使用低29位表示workerCount容量,因此线程池理论上线程数上限是 536870911
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//能接收任务且能处理阻塞队列中的任务
private static final int RUNNING = -1 << COUNT_BITS;
//不能接收新任务,但可以处理队列中的任务。
private static final int SHUTDOWN = 0 << COUNT_BITS;
//不接收新任务,不处理队列任务。
private static final int STOP = 1 << COUNT_BITS;
//所有任务都终止
private static final int TIDYING = 2 << COUNT_BITS;
//什么都不做
private static final int TERMINATED = 3 << COUNT_BITS;
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//获取线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池的线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
execute 方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); //clt记录着runState和workerCount
if (workerCountOf(c) < corePoolSize) {//判断当前线程数是否小于核心线程
//添加任务,传入任务
if (addWorker(command, true)) {//true:使用corePoolSize判断线程数上限
return;//添加成功直接 reture
}
c = ctl.get();//添加失败重新获取 runState和workerCount
}
//判断线程池处于运行状态,然后添加任务到阻塞队列 workQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();//再次获取 runState和workerCount
//双重校验线程池状态,不处于运行状态从阻塞队列 workQueue中删除任务
if (!isRunning(recheck) && remove(command)) {
reject(command);//执行拒绝策略
} else if (workerCountOf(recheck) == 0) {//当前线程数为 0
//添加任务,任务传入null时,会从阻塞队列workQueue从获取任务。
addWorker(null, false);//false:使用maximumPoolSize判断线程数上限
}
}
//再次添加任务,false:使用maximumPoolSize判断线程数上限
else if (!addWorker(command, false)) {
reject(command);//执行拒绝策略
}
}
总结
先看看 addWork() 的两个参数,第一个是需要提交的任务 Runnable firstTask,第二个参数是 boolean 类型,表示是否为核心线程。
execute 中有三处调用了 addWork方法。
-
第一次:if (workerCountOf© < corePoolSize) 这个很好理解,工作线程数少于核心线程数,提交任务。所以 addWorker(command, true)。
-
第二次:workerCountOf(recheck) == 0 如果worker的数量为0,那就 addWorker(null, false)。为什么这里是 null ?之前已经把 command 提交到阻塞队列了 workQueue.offer(command)。所以提交一个空任务,后面直接从阻塞队列中取就可以了。
-
第三次:如果线程池没有 RUNNING 或者 offer 阻塞队列失败,addWorker(command, false),很好理解,对应的就是,阻塞队列满了,将任务提交到,非核心线程池。
至此,重新归纳execute()的逻辑应该是:
- 如果当前运行的线程,少于corePoolSize,则创建一个新的线程来执行任务。
- 如果运行的线程等于或多于 corePoolSize,判断线程池处于 RUNNING 状态后,将任务加入 BlockingQueue。
- 如果加入 BlockingQueue 成功,需要二次检查线程池的状态。如果线程池没有处于 RUNNING,则从 BlockingQueue 移除任务,启动拒绝策略。
- 如果线程池处于 RUNNING状态,则检查当前线程数是否为 0。如果为0,添 null 任务,这样在创建线程后,线程会直接从阻塞队列中获取任务并执行。
- 如果加入 BlockingQueue 失败,则创建新的线程来处理任务。
- 如果启动线程数大于 maximumPoolSize,任务将被拒绝策略拒绝。
addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);//获取线程池状态
// Check if queue empty only if necessary.
//逻辑比较乱,后面说
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())){
return false;
}
for (;;) {
//获取线程数
int wc = workerCountOf(c);
//判断线程是否超过线程池上限 或者 超过核心线程/最大线程数(根据传入的 core 判断)
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) {
return false;//return false失败
}
if (compareAndIncrementWorkerCount(c)) {//CAS操作,线程数+1
break retry;//如果增加成功,则跳出
}
c = ctl.get();//线程数增加失败,则再次获取 线程池状态
if (runStateOf(c) != rs) {//线程池状态发生改变,重新再来
continue retry;
}
// else CAS failed due to workerCount change; retry inner loop
}
}
//***************上面的逻辑是考虑是否能够添加线程,如果可以就cas的增加工作线程数量****************
//**********************************下面正式启动线程**************************************
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//创建 worker
final Thread t = w.thread;//获取 worker的线程
if (t != null) {
//获取可重入锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//锁住
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());//获取线程池状态
if (rs < SHUTDOWN || //线程池处于 RUNNING状态
(rs == SHUTDOWN && firstTask == null)) {//或者线程处于SHUTDOWN状态,但是firstTask为 null
if (t.isAlive()) {// precheck that t is startable
// 当前线程已经启动,抛出异常
throw new IllegalThreadStateException();
}
workers.add(w);//把worker添加到集合中,workers是 HashSet必须在lock的情况下操作。
int s = workers.size();
if (s > largestPoolSize) {//largestPoolSize 记录线程池中出现过的最大线程数量
largestPoolSize = s;
}
workerAdded = true;//标记workAdded为 true
}
} finally {
mainLock.unlock();//解锁
}
if (workerAdded) {
t.start();//如果添加成功,启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted) {//启动线程失败,回滚。
addWorkerFailed(w);
}
}
return workerStarted;
}
代码分析
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) {
return false;
}
这段逻辑比较绕,不过可以拆分来看,其实就是前后条件都为 true 时会 return false 拒绝添加线程。
- rs >= SHUTDOWN
判断线程池状态处于 SHUTDOWN、STOP、TIDYING、TERMINATED 状态下。 - ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
判断线程池状态处于 SHUTDOWN;判断传入任务为 null;判断阻塞队列不为空;
三个条件都为 true 时会对外输出 true,经过取反。总体两个条件都为 false,未命中条件。向下执行添加线程任务。
什么意思呢? - 就是如果线程池处于RUNNING状态下可以添加任务,无需 return,继续向后执行。
- 如果线程池处于STOP、TIDYING、TERMINATED中的一种,无法添加任务,直接 return false。
- 如果线程池处于SHUTDOWN(无法添加任务,但是可以执行阻塞队列中的任务),判断firstTask == null(firstTask为 null 时会从阻塞队列中获取任务执行),阻塞队列不为空。
这样RUNNING状态下可以添加任务,SHUTDOWN状态下可以执行队列中的任务,其他状态不可添加任务,也不可以执行队列中的任务。
Worker类
Worker 是什么?
w = new Worker(firstTask);
final Thread t = w.thread;
t.start();
从上面 addWorker(Runnable firstTask, boolean core)方法中可以看到,会创建Worker对象,并从 Worker对象中获取线程,调用 start()方法开始执行线程,下面看看 Worker是什么东西?
private final class Worker
extends AbstractQueuedSynchronizer implements Runnable {
可以看到它继承了AQS并发框架还实现了Runnable。证明它还是一个线程任务类。那么我们调用t.start()事实上就是调用了该类重写的run方法。而AQS是用来构建锁或者其他同步组件的基础框架。因为需要实现不可重入的特性去反应线程当前的执行状态,所以无法使用 ReentrantLock 可重入锁。
对于线程来说,如果线程正在执行是不允许其它锁重入进来的。
线程只需要两个状态:
一个是独占锁,表明正在执行任务;
一个是不加锁,表明是空闲状态;
runWorker 方法
//在 run 方法中调用了 runWorker方法,下面看看 runWorker 方法
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//获取当前线程
Runnable task = w.firstTask;//得到传入的 task任务,某些情况下为null,前面讲过
//将Worker.firstTask置空
w.firstTask = null;
//由于Worker初始化时,AQS中state设置为-1,所以先解锁一次把state 更新为0,允许线程中断
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try{
//循环判断任务是否为空(firstTask 或者 getTask()从阻塞队列中获取 task)
while (task != null || (task = getTask()) != null) {
w.lock();//上锁
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
&& !wt.isInterrupted()) {
//只有线程池>=stop, 且线程没有被中断,才会调用interrupt中断线程
wt.interrupt();//中断当前线程
}
try {
beforeExecute(wt, task);//线程执行之前调用
Throwable thrown = null;
try {
task.run();//执行run方法(Runable对象)
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);//线程执行之后调用
}
} finally {
//执行完后, 将task置空, 完成任务++,释放锁,继续循环尝试获取任务
task = null;
w.completedTasks++;
w.unlock();
}
}
//没有任务(firstTask==null && getTask() == null)退出工作
completedAbruptly = false;
} finally {
//处理线程退出,主要就是从 workers 中删除 Worker
processWorkerExit(w, completedAbruptly);
}
}
代码分析
if((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
&& !wt.isInterrupted()) {
//只有线程池>=stop, 且线程没有被中断,才会调用interrupt中断线程
wt.interrupt();//设置中断当前线程 true
}
runStateAtLeast 方法判断,线程池状态是否为STOP、TIDYING、TERMINATED(这三种状态下都不再处理任何任务)。
线程池>= STOP 为 true。由于使用的是 || 短路或,条件会演变成这样。
if (runStateAtLeast(ctl.get(), STOP) && !wt.isInterrupted())
-
线程池 >= STOP 为 true。会出现下面两种情况
1.线程池 >= STOP 为 true,! 线程没有被中断 为 true,直接调用interrupt中断线程。
2.线程池 >= STOP 为 true,! 线程已经被中断 为 false,无需重复中断线程。 -
线程池 >= STOP 为 false。会出现下面两种情况
1.线程池 RUNING 或 SHUTDOWN,但线程没有被中断,继续执行任务。
2.线程池 RUNING 或 SHUTDOWN,但线程已经被中断,没有必要重复中断线程。
其实只有一种情况才会命中 if 条件。就是线程池处于不再执行任务的状态下,并且线程也没有被中断,才会自己主动中断线程。
总结
总结一下runWorker方法的执行过程:
- while循环中,不断的判断 task 任务是否为空(firstTask 或者 getTask() 从阻塞队列 workerQueue 中获取 task);
- 如果线程池正在停止,则中断线程。否则调用(3);
- 调用 task.run() 执行任务;
- 如果 task 为null 则跳出循环,执行 processWorkerExit() 方法,销毁线程workers.remove(w)。
getTask 方法
/**
* 找到一个可以执行的任务
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/**
* 1.线程池状态 >= STOP,不执行任务,线程数-1,返回 null。
* 2.线程池状态 == SHUTDOWN
* 2.1 workQueue 有待执行的任务,未命中条件,继续向下查询任务。
* 2.2 workQueue没有待执行的任务, 线程数-1,返回 null。
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//允许核心线程销毁 或者 当前线程数大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//线程数 > 最大线程数 或者 timed == true && timedOut == ture
//线程数 >1 或者 阻塞队列为空
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//满足条件 CAS 线程数-1,返回 null
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//timed为 true,通过 poll方法做超时拉取,keepAliveTime时间内没有等到有效任务,返回 null
//timed为 false,通过 take方法做阻塞拉取,会阻塞到 拿到下一个有效任务时再返回(一般不为 null)
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
线程池流程图
盗个图,线程池的流程图
文章部分内容从以下博客引用,感谢
Java线程池其实看懂了也很简单
面试官:你分析过线程池源码吗?
线程池-addWorker
仅供参考,欢迎指正