• 守护线程和非守护线程


    一、前言

    借鉴文章

    文章1
    文章2

    ①Java提供的两种线程

    Java提供了两种线程: 守护线程和用户线程(非守护线程)

    守护线程(Daemon Thread): 在程序运行时 在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。任何一个守护线程都是整个JVM中所有非守护线程的“保姆”。

    • 守护线程是一种在后台提供服务的线程,它的存在并不会阻止Java虚拟机(JVM)的退出。

    • 当所有的非守护线程执行完毕或者终止时,JVM会检查是否还有任何活动的守护线程。如果只剩下守护线程,JVM会优雅地关闭守护线程并退出。

    • 可以通过调用Thread类的setDaemon(true)方法将线程设置为守护线程。

    用户线程(非守护线程,Non-daemon Thread): 用户线程基本上和守护线程一样,唯一的不同之处在于如果用户线程全部退出运行,只剩下守护线程存在了,JVM就会退出。因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做,当然也就没有继续执行的必要了,程序就会终止,同时会杀死所有的"守护线程",也就是说只要有任何非守护线程还在运行,程序就不会终止

    • 非守护线程是一种正常的线程,它的存在会阻止JVM的退出,直到所有的非守护线程执行完毕或者手动调用System.exit()方法退出程序。
    • 默认情况下,通过Thread类创建的线程都是非守护线程。

    ②JVM正常情况的退出

    JDK官方文档:

    The Java Virtual Machine exits when the only threads running are all daemon threads.

    当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。

    ③守护线程应用场景(作用)

    当JVM中没有一个正在运行的非守护线程,这个时候JVM就会退出,也就是说守护线程拥有自动结束自己声明周期的特性,而非守护线程不具备这个特点JVM中的垃圾回收线程就是典型的守护进程,当非守护线程都没有了,那要垃圾回收线程干什么,直接结束JVM即可。守护线程一般用于执行一些后台任务,在程序或JVM退出时候,守护线程能够自动关闭,具体场景如下:

    1. 后台任务处理:守护线程通常用于执行一些后台任务,这些任务对于整个程序的运行并不是必需的,而且在程序的主要逻辑结束时可以被安全地终止。例如,日志记录、性能统计、定时任务等可以作为守护线程来执行。
    2. 服务监控和管理:守护线程常常被用来监控和管理其他线程或资源,确保它们的正常运行。例如,在服务器应用中,可以使用守护线程来监控网络连接、数据库连接等资源,及时释放无用资源或重新尝试连接。
    3. 辅助性工作:一些辅助性的工作可以交给守护线程来完成,比如周期性的数据清理、定时任务的执行等。
    4. 资源回收:守护线程通常用于执行垃圾回收(Garbage Collection)工作,确保程序的内存得到及时的回收和释放,以避免内存泄漏问题。

    ④Hook线程(下面会用到)

    Hook线程概念:

    Hook线程即钩子(Hook)线程,在程序即将退出,即JVM程序即将退出的时候,Hook线程会被启动执行。

    Hook线程代码示例:

    package com;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author banana
     * @create 2023-11-07 16:27
     */
    public class HookTask {
        public static void main(String[] args) {
            //为应用程序注入Hook(钩子)线程
            Runtime.getRuntime().addShutdownHook(new Thread(() ->{
                System.out.println("the hook thread 1 is running.");
                try {
                    //休眠2秒
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("the hook thread 1 will exit");
            }));
            
            System.out.println("the main thread will exit.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    运行结果:

    可以看出,当主线程结束的,也就是JVM进程即将退出的时候,注入的Hook线程启动并打印相关日志。

    在这里插入图片描述

    注意事项:

    1. Hook 线程只有在正确接收到退出信号时,才能被正确执行,如果你是通过 kill -9这种方式,强制杀死的进程,那么抱歉,进程是不会去执行 Hook 线程的,为什么呢?你想啊,它自己都被强制干掉了,哪里还管的上别人呢?
    2. 请不要在 Hook 线程中执行一些耗时的操作,这样会导致程序长时间不能退出。

    二、守护线程实例

    ①非守护线程不退出情况

    代码:

    public class SHOUHUXCTS {
    
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //睡眠一会
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("当前线程:" + Thread.currentThread().getName());
                    }
                }
            });
    
            //启动用户线程(这里是非守护线程)
            thread.start();
            //主线程退出
            System.out.println("The main thread ready to exit…");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:

    可以看到,主线程main thread已经结束了,但是我们自定义的线程(非守护线程)是会持续运行的,即JVM进程依赖没有退出,当前非守护线程仍旧在运行中。

    在这里插入图片描述

    ②守护线程退出情况

    代码:

    package com.yjy;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author banana
     * @create 2023-11-07 15:44
     */
    public class SHOUHUXCTS {
    
        public static void main(String[] args) {
            //设置一个钩子线程,在JVM退出时输出日志
            Runtime.getRuntime()
                    .addShutdownHook(new Thread(() -> System.out.println("The JVM exit success")));
    
    
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        //睡眠一会
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("当前线程:" + Thread.currentThread().getName());
                    }
                }
            });
    
            //设置当前线程为守护线程
            thread.setDaemon(true);
    
            //启动用户线程(这里是非守护线程)
            thread.start();
    
            //主线程退出
            System.out.println("The main thread ready to exit…");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行结果:

    可以看到当主线程退出时,JVM会随之退出运行,守护线程也同时会被回收。

    在这里插入图片描述

  • 相关阅读:
    ansible镜像构建使用
    C++ Qt 学习(六):Qt http 编程
    借助 Terraform 功能协调部署 CI/CD 流水线-Part2
    作为一名python开发者,想要兼职接单,需要学那些技术?要达到什么水准?为什么要学这些技术?
    SpringAOP(面向切面编程)的使用和原理
    【QT】使用toBase64方法将.txt文件的明文变为非明文(类似加密)
    Unity的unity_ObjectToWorld里的每一列分别代表什么意思?换个方向反向理解-更简单
    如何应对访问国外服务器缓慢的问题?SDWAN组网是性价比之选
    AUTOSAR实战篇:手把手带你搞定Watchdog协议栈
    windows10 使用WSL2安装原生docker
  • 原文地址:https://blog.csdn.net/Bananaaay/article/details/134271496