vue项目接口域名动态的获取方法
251
2022-08-23
Java线程池复用线程的秘密你知道吗
目录前言源码探究execute方法addWorker方法Worker类实现了Runnable接口重要属性构造方法run方法执行流程总结
前言
我们都知道线程池可以帮我们管理线程,重复利用线程执行不同的任务。正常情况下,我们创建的线程执行完任务后就会自行销毁,那么线程池是如何做到复用线程的呢?
源码探究
我们从线程池ThreadPoolExecutor源码入手,一探究竟。为了突出重点,以下的方法源码过滤了部分无关代码,以求逻辑清晰。
execute方法
那就从线程池执行的execute方法入手吧!来看一下方法的源码
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1.小于核心线程数时,创建线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.达到核心线程数,不超过队列界限时,添加到队列
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);
}
//3.队列已满,不超过最大线程数时,创建线程
else if (!addWorker(command, false))
//4.达到最大线程数时,执行拒绝策略
reject(command);
}
线程池执行的4个步骤相信大家已经有所了解,这里我们只看添加线程的方法addWorker()
addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//1.创建Worker,传入任务
w = new Worker(firstTask);
//2.取出执行任务的线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
http:// if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//3.执行线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
参数解释:
core:true表示添加的是核心线程,false表示添加的非核心线程
这里大家只需要关心这3行加注释的代码就可以了
就是Worker管理了创建的线程和这个线程执行的第一个任务,并且在addWorker方法中调用线程的http://start方法,开启线程执行了任务。下面我们看看Worker这个类
Worker类
实现了Runnable接口
Worker 是线程池ThreadPoolExecutor的内部类,实现了Runnable接口
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
重要属性
他有两个核心关键的属性,即封装了线程池的线程和要执行的任务,达到了线程和任务解耦的目的。
final Thread thread;
Runnable firstTask;
构造方法
addWorker方法会执行创建一个worker
w = new Worker(firstTask);
看一下Worker的构造方法
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
可以看到 新创建的Worker本身也是一个Runnable,他的thread传的runnable任务就是worker本身
在addWorker方法,最终会取到worker的thread属性,然后启动这个thread
w = new Worker(firstTask);
final Thread t = w.thread;
... ...
t.start();
run方法
刚才介绍过,worker的thread的runnable参数传的就是worker本身,就是调的worker的run方法,现在我们来看最核心的worker的run方法
public void run() {
runWorker(this);
}
调的是ThreadPoolExecutor的runWorker方法
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
try {
while (task != null || (task = getTask()) != null)
{
...
task.run();
...
可以看到runWorker核心是一个while循环,执行了第一个task之后,就不停的从队列中取任务,直到没有任务了才会执行完,销毁线程
执行流程
execute方法调用addWorker方法,并且执行worker.thread.start()开启线程
——》worker.thread 执行worker本身的run方法(worker实现了Runnable接口)
——》执行ThreadPoolExecutor的runWorker方法,是个while循环,执行完worker本身的第一个任务之后,就不停从队列取任务,直到没有任务,执行完,退出循环,销毁
总结
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~