编程界的小学生
- 如果下面的问题你都会的话就别在这浪费时间啦
- 1、execute全貌
- 2、拆解第一步
-
- 2.1、NPE判断
- 2.2、获取ctl
- 2.3、开启线程执行任务
- 2.4、小结
- 3、拆解第二步
-
- 3.1、将任务加到任务队列
- 3.2、recheck
- 3.3、检查是否设置了允许核心线程超时
- 3.4、小结
- 4、拆解第三步
-
- 4.1、开启非核心线程处理任务
- 5、总结
author:编程界的小学生
date:2021/05/30
flag:不写垃圾没有营养的文章!
由于之前写过线程池状态是怎么存储计算的文章,所以此处不在废话。
如不清楚线程池的状态是怎么玩的,是如何获取线程池状态和线程池当前活跃线程数的,请看下面这篇文章。
https://mp.weixin.qq.com/s/zUfMpZp4Zy3jx8oeQzt4PQ
如果下面的问题你都会的话就别在这浪费时间啦-
从源码层面聊聊线程池的运行流程?
-
new一个线程池会开启多少个线程?(套路!不会开启,只有在execute的时候才会开启!)
-
execute方法的recheck的目的是什么?
-
如果添加任务到任务队列后,线程池突然被其他线程给shutdown了,那么这个刚加进去的任务会怎样?
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
2、拆解第一步
2.1、NPE判断
if (command == null)
throw new NullPointerException();
没啥说的,参数安全校验。
2.2、获取ctl
int c = ctl.get();
如不清楚ctl是什么?线程池的状态是怎么获取的?线程池中当前活跃线程数是多少?,请看下面这篇文章。
https://mp.weixin.qq.com/s/zUfMpZp4Zy3jx8oeQzt4PQ
2.3、开启线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
-
如果当前线程池活跃线程数小于用户传递进来的核心线程数,则
addWorker();
-
addWorker()
的意思是:开启一个线程去执行当前任务。源码下篇分析。 -
重新获取ctl(ctrl:高3位代表线程池状态,低29位代表当前线程池活跃线程数)
2.4、小结
// 1.如果执行任务是空,则npe。安全检查。
if (command == null)
throw new NullPointerException();
// 2.获取ctl,目的是下面获取线程池状态以及线程池当前活跃线程数来用
int c = ctl.get();
/*
* workerCountOf(c):当前线程池中活跃线程数
* corePoolSize:核心线程数
*
* 3.如果当前线程池中活跃线程数小于用户设置的核心线程数,则开启一个新线程来执行任务。
*/
if (workerCountOf(c) < corePoolSize) {
// 3.1 开启一个线程来执行任务。
if (addWorker(command, true))
return;
// 因为当前线程池有变化(线程数或者线程池状态),所以重新获取ctl
// 注意这里说的有变化并不仅仅是刚才开启一个新线程,然后线程数+1导致了ctl的变化,还有可能就是因为是多线程环境的,ctl随时都可能被其他线程给改变。
c = ctl.get();
}
3、拆解第二步
3.1、将任务加到任务队列
if (isRunning(c) && workQueue.offer(command)) {...}
能走到这里就代表当前线程池中活跃线程数大于核心线程数了,所以:如果当前线程池中活跃线程数大于核心线程数,且线程池状态是running状态,则将任务offer到任务队列里,如果任务队列满了,则返回false,也就是走【4、拆解第三步】的流程。如果添加成功,则继续下面的recheck操作。
3.2、recheck
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
为什么要recheck线程池状态?
因为上面offer成功后,很有可能突然被其他线程把这个线程池给shutdown了,所以这里recheck,二次检查线程池是否还是RUNNINT状态,如果不是RUNNING了,则remove掉刚才offer进去的任务,并且执行拒绝策略,也就是说如果添加任务到队列后,突然线程池被其他线程给shutdown了,则移除任务,且执行拒绝策略
3.3、检查是否设置了允许核心线程超时
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
这什么神仙操作?能走到这里的话早就代表核心线程数满了,怎么还判断线程池中线程数是不是0?
因为…线程池是允许设置核心线程允许超时的(allowCoreThreadTimeOut
),所以Doug Lea
老爷子在这里再次判断,很严谨,很细腻。如果没线程了,则addWorker(null, false);
,为什么是null?null在addWorker
里有判断用处。下文分析。
3.4、小结
// 1.如果当前线程池中活跃线程数大于核心线程数,且线程池状态是running状态,则将任务offer到任务队列里,如果任务队列满了,则返回false,也就是走【4、拆解第三步】的流程。如果添加成功,则继续下面的recheck操作。
if (isRunning(c) && workQueue.offer(command)) {
// 2. 重新获取线程池ctl,为啥重新获取?反复强调几次了,因为Java是多线程的,随时可能被改变。
// 拿到后赋值给个局部变量也很细腻,因为不想随时都是变化的,相当于事务一样,我此刻拿到了,你们再变我也不管,我拿着我的快照来用
int recheck = ctl.get();
// 3. 如果添加任务到队列后,突然线程池被其他线程给shutdown了,则移除任务,且执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 4. 如果设置了allowCoreThreadTimeOut(允许核心线程数超时),导致现在线程池中活跃线程数是0,则addWorker(null, false);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
4、拆解第三步
4.1、开启非核心线程处理任务
// 能走到这里,证明如下前提:
// 1.核心线程数满了
// 2.任务队列满了
// 所以走到了这里,addWorker(command, false)的含义是开启一个非核心线程来执行这个任务
else if (!addWorker(command, false))
// 如果非核心线程也满了,则拒绝策略。
reject(command);
5、总结
-
如果任务是空,抛出空指针异常。(参数校验)
-
获取ctl,通过ctl获取线程池中活跃线程数,看线程数是不是小于核心线程数
-
如果小于核心线程数则开启一个核心线程去执行这个任务
-
如果当前线程池中活跃线程数大于核心线程数,则将任务添加到任务队列
-
添加到任务队列成功后进行recheck,主要看线程池是不是被其他线程给shutdown了,如果是的话,则remove掉刚才offer进去的任务
-
最后还是检查核心线程数是不是都超时了导致线程池中活跃线程数为0,如果为0,则addWorker一个null进去。
-
如果核心线程数和任务队列都满了,则开启非核心线程去执行任务
-
如果非核心线程达到了最大线程数,则拒绝策略。