普通main方法 springboot项目main方法

Q:普通main方法执行完之后程序就停止了 但是启动springboot项目为什么就没有停止

A:

核心原因:Spring Boot创建了非守护线程

1. main方法执行后发生了什么

java 复制代码
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        // 这里SpringApplication.run()会创建非守护线程
    }
}

2. SpringApplication.run()的工作原理

java 复制代码
// Spring Boot源码简化示意
public ConfigurableApplicationContext run(String... args) {
    // 1. 准备环境
    // 2. 创建应用上下文
    // 3. 刷新上下文(关键步骤)
    refreshContext(context);
    // 4. 执行Runner
    // 5. 返回上下文(但此时main线程已完成,程序继续运行)
}

3. 核心机制:嵌入式Web服务器创建非守护线程

Spring Boot启动时会根据依赖创建相应的Web服务器:

java 复制代码
// Tomcat为例
public class TomcatWebServer {
    public void start() throws WebServerException {
        // Tomcat会启动线程池,这些线程都是非守护线程
        this.tomcat.start();
        // 启动一个非守护线程来保持服务器运行
        startDaemonAwaitThread();
    }
    
    private void startDaemonAwaitThread() {
        Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
            @Override
            public void run() {
                // 这个线程会一直阻塞,直到服务器关闭
                TomcatWebServer.this.tomcat.getServer().await();
            }
        };
        awaitThread.setDaemon(false); // 非守护线程!
        awaitThread.start();
    }
}

4. Java线程的守护/非守护机制

java 复制代码
public class ThreadExample {
    public static void main(String[] args) {
        // 守护线程:主线程结束后会自动结束
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("Daemon running...");
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();
        
        // 程序会立即结束,因为只有守护线程
    }
}

5. Spring Boot保持运行的详细流程

text 复制代码
Spring Boot启动流程:
1. main线程调用 SpringApplication.run()
2. 创建Spring容器和应用上下文
3. 根据类路径检测到Web依赖(如spring-boot-starter-web)
4. 启动嵌入式Web服务器(Tomcat/Netty/Jetty)
5. Web服务器创建并启动非守护线程:
   - 监听端口(如8080)
   - 处理HTTP请求
   - 保持连接
6. main线程执行完毕,但JVM检测到还有非守护线程在运行
7. JVM保持运行,直到所有非守护线程结束

6. 验证:查看Spring Boot的线程

java 复制代码
// 可以在Spring Boot启动后查看线程
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        
        // 查看所有线程
        Thread.getAllStackTraces().keySet().forEach(thread -> {
            System.out.println(thread.getName() + " - 是否守护线程: " + thread.isDaemon());
        });
    }
}

输出示例:

复制代码
main - 是否守护线程: false
Tomcat-startStop-1 - 是否守护线程: false
http-nio-8080-Acceptor - 是否守护线程: false
http-nio-8080-ClientPoller - 是否守护线程: false
...

7. 如何优雅关闭Spring Boot应用

java 复制代码
// 方式1:调用close()方法
context.close();

// 方式2:发送关闭信号(如Ctrl+C)
// Spring Boot会注册JVM关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    context.close();
}));

// 方式3:通过Actuator端点
// POST /actuator/shutdown

总结

Spring Boot项目在main方法执行完后不停止的原因是:

  1. Web服务器创建了非守护线程来监听端口和处理请求
  2. JVM规则:只要还有非守护线程在运行,JVM就不会退出
  3. 事件驱动模型:Web服务器在等待HTTP请求,处于阻塞状态
  4. 优雅的生命周期管理:需要显式关闭或接收关闭信号才会停止

这确保了Spring Boot应用可以作为独立的服务器进程长期运行。

相关推荐
IT_陈寒3 小时前
用了Proxy才发现以前的JavaScript白写了
前端·人工智能·后端
唐青枫3 小时前
别把 ArrayList 当成会自动管理内存的 List:Zig 动态数组从入门到实战
后端
鸿蒙开发3 小时前
我为 HarmonyOS 做了一个统一大模型 SDK:@hmkit/ai 正式开源
后端
猪是念来过倒3 小时前
Semaphore 与 RateLimiter:并发控制双雄详解
后端
掘金者阿豪3 小时前
异构数据同步最怕什么?不是同步慢,而是数据对不上
后端
古法安卓4 小时前
Android-日志系统源码解析
android·java·android studio
程序员cxuan4 小时前
Claude 官方的学习教程,太强了。
人工智能·后端·程序员
老孙讲技术4 小时前
把工地遮挡和掉线接进项目部:setMessageCallback 订 alarm 与 deviceStatus
后端·物联网·音视频开发
老孙讲技术4 小时前
关店后有人进门,监控值班群却没响:用 setMessageCallback 订 human,再用 aiHuman 打开人形
后端·物联网·音视频开发
LEE5 小时前
原来 Claude Code 最厉害的工具,一行代码都不写
前端·后端