Java线程池复用线程的秘密你知道吗

网友投稿 240 2022-10-24

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小时内删除侵权内容。

上一篇:docker容器技术进阶知识点
下一篇:docker学习之——获取和推送镜像
相关文章

 发表评论

暂时没有评论,来抢沙发吧~