面试官:Tomcat 在 SpringBoot 中是如何启动的

Posted Think_Higher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官:Tomcat 在 SpringBoot 中是如何启动的相关的知识,希望对你有一定的参考价值。

文章目录


我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动,本文将以Tomcat为例,来看看SpringBoot是如何启动Tomcat的,同时也将展开学习下Tomcat的源码,了解Tomcat的设计。

从 Main 方法说起

用过SpringBoot的人都知道,首先要写一个main方法来启动

@SpringBootApplication
public class TomcatdebugApplication 
   

    public static void main(

SpringBoot——嵌入式tomcat

面试官:知道线程池吗

我:知道啊,然后准备回答,线程池原理及实现流程

面试官打断:最近面试人挺多的,基本都知道原理,你给我讲讲keepAliveTime具体是怎样实现非核心线程过期回收的。

我:.....(黑人问号)

面试完,迫不及待的打开源码后,发现之前看源码已经看过线程过期后怎样销毁的,但主要是想弄懂线程复用的原理,所以没有注意。

前文源码学习:Java并发包中的线程池ThreadPoolExecutor

一、线程过期销毁

 1、Worker.run():线程池线程复用的原理

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); //将state设置为0,允许中断 allow interrupts
        boolean completedAbruptly = true;
        try {
            //这里就是线程复用的实质,线程执行完提交的任务后,会不停从阻塞队列workerQueue中获取任务再执行
//跳出这里的循环,线程就不会复用,执行完runWorker方法后会被销毁
while (task != null || (task = getTask()) != null) { w.lock();//worker不可重入独占锁 // 如果线程池正在停止Stopping,确保线程中断 // 如果没有,确保线程没有被中断 // 第二种情况需要复查处理 // 清除中断时立即停止比赛 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //执行任务前操作--空方法预留扩展 beforeExecute(wt, task); Throwable thrown = null; try { //实际的任务运行command.run() task.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; //当前线程完成任务数更新++ w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { //执行清理工作, //将当前线程完成任务数累加到总任务数上 //从workerSet中删除当前工作线程 //尝试终止线程池 //如果当前线程个数小于核心个数,创建线程 processWorkerExit(w, completedAbruptly); } }

2、线程过期销毁

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //开始执行前会将work.firstTask = null,核心线程、非核心线程都是
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //一次循环后task == null,所以线程过期销毁判断关键在于getTask()
            while (task != null || (task = getTask()) != null) {
                w.lock();
                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();
                    } 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
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

ThreadPoolExecutor.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.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//如果工作队列为空,线程池线程数减1,并返回null decrementWorkerCount();
return null; } int wc = workerCountOf(c); //worker工作线程会不会被回收
//① allowCoreThreadTimeOut == true,核心线程和非核心线程空闲keepAliveTime被销毁,workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
//② allowCoreThreadTimeOut == false,非核心线程空闲keepAliveTIme销毁,workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
// ,核心线程会被阻塞,不会被销毁workQueue.take()
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }

所以keepAliveTime具体是怎样实现非核心线程过期销毁的,是个很简单的问题。有点后悔没答上来

workQueue队列为空,跳出循环,线程就会被销毁。具体代码通过阻塞队列workQueue(keepAliveTime,TimeUnit.NANOSECONDS)实现控制线程存活时间的

线程销毁的两种场景:

① allowCoreThreadTimeOut == true,核心线程和非核心线程空闲keepAliveTime时被销毁,workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
② allowCoreThreadTimeOut == false,非核心线程空闲keepAliveTIme时被销毁,workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)

核心线程永不销毁的场景

① allowCoreThreadTimeOut == false,核心线程永不销毁。workQueue.take()阻塞当前线程。

以上是关于面试官:Tomcat 在 SpringBoot 中是如何启动的的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot——嵌入式tomcat

面试官问:你们服务最大的并发量是多少?

面试官:如何手写一个Spring Boot Starter?

面试官:如何在自定义端口上运行 Spring Boot 应用程序?

「高并发秒杀」来自大厂面试官的MySQL灵魂十连问

Tomcat相关面试题,看这篇就够了!保证能让面试官颤抖!