最全面试官问:线程池如何重复利用空闲的线程来执行任务?,javamap集合面试题

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

image

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

⑤ workQueue

顾名思义,其指代任务队列:用来保存等待执行任务的阻塞队列。

⑥ threadFactory

顾名思义,其指代创建线程的工厂:可以通过线程工厂给每个创建出来的线程设置更加有意义的名字。

⑦ RejectedExecutionHandler

顾名思义,其指代拒绝执行程序,可以理解为饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK1.5中Java线程池框架提供了以下4种策略。

  • AbortPolicy:直接抛出异常RejectedExecutionException。

  • CallerRunsPolicy:只用调用者所在线程来运行任务,即由调用 execute方法的线程执行该任务。

  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

  • DiscardPolicy:不处理,丢弃掉,即丢弃且不抛出异常。

这7个参数共同决定了线程池执行一个任务的策略:

当一个任务被添加进线程池时:

  1. 线程数量未达到 corePoolSize,则新建一个线程(核心线程)执行任务

  2. 线程数量达到了 corePools,则将任务移入队列等待

  3. 队列已满,新建线程(非核心线程)执行任务

  4. 队列已满,总线程数又达到了 maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常

说白了就是先利用核心线程,核心线程用完,新来的就加入等待队列,一旦队列满了,那么只能开始非核心线程来执行了。

上面的策略,会在阅读代码的时候体现出来,并且在代码中也能窥探出真正复用空闲线程的实现原理。

接下来我们就从线程池执行任务的入口分析。

一个线程池可以接受任务类型有Runnable和Callable,分别对应了execute和submit方法。目前我们只分析execute的执行过程。

上源码:

public void execute(Runnable command) {

if (command == null)

throw new NullPointerException();

/*

* Proceed in 3 steps:

*

* 1. If fewer than corePoolSize threads are running, try to

* start a new thread with the given command as its first

* task.  The call to addWorker atomically checks runState and

* workerCount, and so prevents false alarms that would add

* threads when it shouldn’t, by returning false.

*

* 2. If a task can be successfully queued, then we still need

* to double-check whether we should have added a thread

* (because existing ones died since last checking) or that

* the pool shut down since entry into this method. So we

* recheck state and if necessary roll back the enqueuing if

* stopped, or start a new thread if there are none.

*

* 3. If we cannot queue task, then we try to add a new

* thread.  If it fails, we know we are shut down or saturated

* and so reject the task.

*/

int c = ctl.get();

if (workerCountOf© < corePoolSize) { //第一步:如果线程数量小于核心线程数

if (addWorker(command, true))//则启动一个核心线程执行任务

return;

c = ctl.get();

}

if (isRunning© && workQueue.offer(command)) {//第二步:当前线程数量大于等于核心线程数,加入任务队列,成功的话会进行二次检查

int recheck = ctl.get();

if (! isRunning(recheck) && remove(command))

reject(command);

else if (workerCountOf(recheck) == 0)

addWorker(null, false);//启动非核心线程执行,注意这里任务是null,其实里面会去取任务队列里的任务执行

}

else if (!addWorker(command, false))//第三步:加入不了队列(即队列满了),尝试启动非核心线程

reject(command);//如果启动不了非核心线程执行,说明到达了最大线程数量的限制,会使用第7个参数抛出异常

}

代码并不多,主要分三个步骤,其中有两个静态方法经常被用到,主要用来判断线程池的状态和有效线程数量:

// 获取运行状态

private static int runStateOf(int c)     { return c & ~CAPACITY; }

// 获取活动线程数

private static int workerCountOf(int c)  { return c & CAPACITY; }

总结一下,execute的执行逻辑就是:

  • 如果 当前活动线程数 < 指定的核心线程数,则创建并启动一个线程来执行新提交的任务(此时新建的线程相当于核心线程);

  • 如果 当前活动线程数 >= 指定的核心线程数,且缓存队列未满,则将任务添加到缓存队列中;

  • 如果 当前活动线程数 >= 指定的核心线程数,且缓存队列已满,则创建并启动一个线程来执行新提交的任务(此时新建的线程相当于非核心线程);

从代码中我们也可以看出,即便当前活动的线程有空闲的,只要这个活动的线程数量小于设定的核心线程数,那么依旧会启动一个新线程来执行任务。也就是说不会去复用任何线程。在execute方法里面我们没有看到线程复用的影子,那么我们继续来看看addWorker方法。

private boolean addWorker(Runnable firstTask, boolean core) {

retry:

for (;😉 {

int c = ctl.get();

int rs = runStateOf©;

// Check if queue empty only if necessary.

if (rs >= SHUTDOWN &&

! (rs == SHUTDOWN &&

firstTask == null &&

! workQueue.isEmpty()))

return false;

for (;😉 {

int wc = workerCountOf©;

if (wc >= CAPACITY ||

wc >= (core ? corePoolSize : maximumPoolSize))

return false;

if (compareAndIncrementWorkerCount©)

break retry;

c = ctl.get();  // Re-read ctl

if (runStateOf© != rs)

continue retry;

// else CAS failed due to workerCount change; retry inner loop

}

}

//前面都是线程池状态的判断,暂时不理会,主要看下面两个关键的地方

boolean workerStarted = false;

boolean workerAdded = false;

Worker w = null;

try {

w = new Worker(firstTask); // 新建一个Worker对象,这个对象包含了待执行的任务,并且新建一个线程

final Thread t = w.thread;

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 ||

(rs == SHUTDOWN && firstTask == null)) {

if (t.isAlive()) // precheck that t is startable

throw new IllegalThreadStateException();

workers.add(w);

int s = workers.size();

if (s > largestPoolSize)

largestPoolSize = s;

workerAdded = true;

}

} finally {

mainLock.unlock();

}

if (workerAdded) {

t.start(); // 启动刚创建的worker对象里面的thread执行

workerStarted = true;

}

}

} finally {

if (! workerStarted)

addWorkerFailed(w);

}

return workerStarted;

}

方法虽然有点长,但是我们只考虑两个关键的地方,先是创建一个worker对象,创建成功后,对线程池状态判断成功后,就去执行该worker对象的thread的启动。也就是说在这个方法里面启动了一个关联到worker的线程,但是这个线程是如何执行我们传进来的runnable任务的呢?接下来看看这个Worker对象到底做了什么。

private final class Worker

extends AbstractQueuedSynchronizer

implements Runnable

{

/**

* This class will never be serialized, but we provide a

* serialVersionUID to suppress a javac warning.

*/

private static final long serialVersionUID = 6138294804551838833L;

/** Thread this worker is running in.  Null if factory fails. */

final Thread thread;

/** Initial task to run.  Possibly null. */

Runnable firstTask;

/** Per-thread task counter */

volatile long completedTasks;

/**

* Creates with given first task and thread from ThreadFactory.

* @param firstTask the first task (null if none)

*/

Worker(Runnable firstTask) {

setState(-1); // inhibit interrupts until runWorker

this.firstTask = firstTask;

this.thread = getThreadFactory().newThread(this);

}

/** Delegates main run loop to outer runWorker. */

public void run() {

runWorker(this);

}

// Lock methods

//

// The value 0 represents the unlocked state.

// The value 1 represents the locked state.

protected boolean isHeldExclusively() {

return getState() != 0;

}

protected boolean tryAcquire(int unused) {

if (compareAndSetState(0, 1)) {

setExclusiveOwnerThread(Thread.currentThread());

return true;

}

return false;

}

protected boolean tryRelease(int unused) {

setExclusiveOwnerThread(null);

setState(0);

return true;

}

public void lock()        { acquire(1); }

public boolean tryLock()  { return tryAcquire(1); }

public void unlock()      { release(1); }

public boolean isLocked() { return isHeldExclusively(); }

void interruptIfStarted() {

Thread t;

if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {

try {

t.interrupt();

} catch (SecurityException ignore) {

}

}

}

}

最重要的构造方法:

Worker(Runnable firstTask) { // worker本身实现了Runnable接口

setState(-1); // inhibit interrupts until runWorker

this.firstTask = firstTask; // 持有外部传进来的runnable任务

//创建了一个thread对象,并把自身这个runnable对象给了thread,一旦该thread执行start方法,就会执行worker的run方法

this.thread = getThreadFactory().newThread(this);

}

在addWorker方法中执行的t.start会去执行worker的run方法:

public void run() {

runWorker(this);

}

run方法又执行了ThreadPoolExecutor的runWorker方法,把当前worker对象传入。

final void runWorker(Worker w) {

Thread wt = Thread.currentThread();

Runnable task = w.firstTask; // 取出worker的runnable任务

w.firstTask = null;

w.unlock(); // allow interrupts

boolean completedAbruptly = true;

try {

// 循环不断的判断任务是否为空,当第一个判断为false的时候,即task为null,这个task啥时候为null呢?

// 要么w.firstTask为null,还记得我们在execute方法第二步的时候,执行addWorker的时候传进来的runnable是null吗?

// 要么是执行了一遍while循环,在下面的finally中执行了task=null;

// 或者执行第二个判断,一旦不为空就会继续执行循环里的代码。

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())

wt.interrupt();

try {

beforeExecute(wt, task);

Throwable thrown = null;

try {

task.run(); // 任务不为空,就会执行任务的run方法,也就是runnable的run方法

} 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 = null; // 执行完成置null,继续下一个循环

w.completedTasks++;

w.unlock();

}

}

completedAbruptly = false;

} finally {

processWorkerExit(w, completedAbruptly);

}

}

方法比较长,归纳起来就三步:

  1. 从worker中取出runnable(这个对象有可能是null,见注释中的解释);

  2. 进入while循环判断,判断当前worker中的runnable,或者通过getTask得到的runnable是否为空,不为空的情况下,就执行run;

  3. 执行完成把runnable任务置为null。

假如我们不考虑此方法里面的while循环的第二个判断,在我们的线程开启的时候,顺序执行了runWorker方法后,当前worker的run就执行完成了。

最后

权威指南-第一本Docker书

引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

image

image

image

image

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

er构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

[外链图片转存中…(img-TsVNxsY0-1715618613827)]

[外链图片转存中…(img-z65ZWQuf-1715618613828)]

[外链图片转存中…(img-nXghn90W-1715618613828)]

[外链图片转存中…(img-2wNkZ6l9-1715618613828)]

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值