• JAVA多线程基础篇--守护线程(Daemon Thread)


    1.概述

    JAVA中的线程主要分为两类:用户线程(User Thread)和守护线程(Daemon Thread)。JAVA语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被称为普通线程。守护线程也被称之为后台线程、服务线程或精灵线程,守护线程是为用户线程服务的,当线程中的用户线程都执行结束后,守护线程也会跟随结束。守护线程具有自动结束生命周期的特性,而非守护线程则不具备该特性。本文将编写一些案例,同时根据源码来对用户线程和守护线程进行分析,帮助大家更好地理解。

    2.守护线程分析

    2.1 案例分析

    上面讲了这么多概念,不如直接上一段代码来看一下:

    import lombok.extern.slf4j.Slf4j;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class DaemonThreadDemo {
    
        public static void main(String[] args) {
            log.info("当前线程:{}", Thread.currentThread().getName());
            //创建一个用户线程,一直运行
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        log.info("当前线程:【{}】,正在运行", Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        log.error("当前线程:{},休眠异常:{}", Thread.currentThread().getName(), e);
                    }
                }
            }, "子线程");
            
            //启动线程
            thread.start();
            
            //主线程休眠2s
            try {
                TimeUnit.SECONDS.sleep(2);
                log.info("主线程:{},休眠2s", Thread.currentThread().getName());
            } catch (InterruptedException e) {
                log.error("主线程:{}休眠异常:{}", Thread.currentThread().getName(), e);
            }
            
    		//主线程结束,打印信息
            log.info("主线程:{},结束运行", Thread.currentThread().getName());
        }
    }
    
    • 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

    上述代码的主要运行流程如下图所示:
    在这里插入图片描述
    主线程main线程启动后,首先会去创建一个用户线程(该用户线程内部是一个死循环,会一直运行),接下来会启动该用户线程,最后主线程休眠并退出。上述代码中,线程启动后,当主线程运行结束后,JVM也不会退出,因为名称为子线程的用户线程还在后台运行,而且如无异常中断会一直运行。运行结果截图如下所示:
    在这里插入图片描述
    如果修改上述代码为守护线程呢?运行结果又会怎样?对上述代码做如下修改:

    //新增监听JVM是否退出的Hook线程,Hook线程能够在JVM程序退出的时候被启动执行
     Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                log.info("JVM运行结束......");
            }));
           
           
     //将线程设置为守护线程
     thread.setDaemon(true);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    修改代码后,运行结果如下图所示:
    在这里插入图片描述
    由上述运行结果可知,当主线运行结束后,JVM也结束了运行,被设置为守护线程的子线程也结束了运行。

    2.2 代码分析

    2.2.1 设置线程为守护线程

    线程可以通过 setDaemon(true) 方法将线程类型更改为守护线程,主要代码如下所示:

    Thread thread = new Thread(()-> {
             @Override
             public void run() {
                 System.out.println("我是子线程");
             }
         });
    //将线程设置为守护线程
     thread.setDaemon(true);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2.2 判断是否为守护线程

    线程可以通过isDaemon()方法来判断当前线程或指定线程是否为守护线程,如果是守护线程则结果为true,否则为false。具体代码如下:

    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class IsDaemonThread {
    
        public static void main(String[] args) {
    
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                log.info("JVM运行结束......");
            }));
    
            Thread thread = new Thread(() -> {
                log.info("线程:{},是否是守护线程:{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
            }, "子线程");
    
            thread.setDaemon(true);
            thread.start();
    
            log.info("主线程:{},IsDaemon:{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行结果为:
    在这里插入图片描述

    2.2.3 判断守护线程的优先级

    在这里插入图片描述
    运行结果如下图所示:
    在这里插入图片描述
    由上述运行结果可知,守护线程和用户线程是同一个优先级。我看部分文章或博客写的是用户线程优先级大于守护线程,如果有详细证明过程,可以推荐给我。

    2.2.4 设置线程池为守护线程

    如果设置线程池为守护线程,则需要将线程池中每个线程都设置为守护线程,具体代码如下:

    import java.util.Set;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    public class ThreadPoolDaemonDemo {
    
        public static void main(String[] args) {
    
            ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    // 设置线程为守护线程
                    t.setDaemon(true);
                    return t;
                }
            });
            
            System.out.println("线程池创建成功!");
            Thread thread1 = new Thread(() -> {
                System.out.println("hello ThreadPoolDaemonDemo1");
            });
          
            threadPool.submit(thread1);
            
            Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
            for (Thread thread : threadSet) {
                System.out.println("thread.name=【" + thread.getName() + "】;group=" + thread.getThreadGroup() + ";isDaemon=" + thread.isDaemon() + ";priority=" + thread.getPriority());
            }
    
        }
    }
    
    • 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

    线程运行结果为:
    在这里插入图片描述
    由上述结果看出,当将线程池设置为守护线程时,其内部线程都是守护线程;为了验证上述猜想,我编写了一个循环,测试了提交不同数量的线程任务,来查看该线程是否属于守护线程,结果表明均为守护线程。修改代码如下:

     for (int i = 0; i < 10; i++) {
                threadPool.submit(thread1);
            }
    
    • 1
    • 2
    • 3

    结果为:
    在这里插入图片描述

    2.2.5 判断守护线程子线程是否守护

    下面编写一段测试代码,来判断守护线程创建的线程是否为守护线程,具体如下:
    在这里插入图片描述
    上述代码的运行结果如下:

    在这里插入图片描述
    由上述运行结果可知:守护线程中创建的子线程,默认情况下也属于守护线程

    2.3 注意事项

    1. setDaemon(true) 必须写在start方法前面;

    setDaemon(true) 如果设置在 start() 之后,不但程序的执行会报错,而且设置的守护线程也不会生效。具体效果如下所示:

     @Slf4j
    public class DaemonThreadDemo {
    
        public static void main(String[] args) {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                log.info("JVM运行结束......");
            }));
    
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        log.info("当前线程:【{}】,正在运行", Thread.currentThread().getName());
                        log.info("当前线程:【{}】,优先级为:{}", Thread.currentThread().getName(), Thread.currentThread().getPriority());
                    } catch (InterruptedException e) {
                        log.error("当前线程:{},休眠异常:{}", Thread.currentThread().getName(), e);
                    }
                }
            }, "子线程");
            thread.start();
            //设置守护线程
            thread.setDaemon(true);
            try {
                TimeUnit.SECONDS.sleep(2);
                log.info("主线程:{},休眠2s", Thread.currentThread().getName());
            } catch (InterruptedException e) {
                log.error("主线程:{}休眠异常:{}", Thread.currentThread().getName(), e);
            }
            log.info("当前线程:【{}】,优先级为:{}", Thread.currentThread().getName(), Thread.currentThread().getPriority());
            log.info("主线程:{},结束运行", Thread.currentThread().getName());
        }
    }
    
    • 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

    在这里插入图片描述

    2.不能把正在运行中的线程设置为守护线程;
    3.复杂计算、资源回收这种,不适合使用守护线程来执行。

    3.小结

    1.守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都结束之后,无论守护线程是否在工作都会跟随用户线程一起结束;
    2.守护线程的子线程也是守护线程;
    3.守护线程的优先级和用户线程优先级一致;
    4.守护线程setDaemon(true) 如果设置在 start() 之后,程序执行会报错,守护线程也不会生效。

    4.应用场景

    1.垃圾回收线程就是典型的守护线程,随主线程结束而结束;
    2.应用指标统计,部分服务可以通过守护线程来采取应用指标,服务结束则停止采集。

    5.参考文献

    1.https://www.jianshu.com/p/a157d749b5e8
    2.https://www.cnblogs.com/quanxiaoha/p/10731361.html
    3.https://juejin.cn/post/7006879369368961061
    4.《JAVA多线程编程核心技术》-高洪岩著

  • 相关阅读:
    Java入门笔记
    网络地址转换技术NAT(第九课)
    数据库基础——4.select语句
    面试题:关于抽象类和接口的理解
    工业设计里的四个细节你知道吗?
    直播回顾|论道原生:云原生存储
    开启数字消费新纪元
    卡塔尔世界杯倒计时!世界杯直播在哪里观看?美家市场汇总来了!
    Qt对话框
    base系列编码
  • 原文地址:https://blog.csdn.net/qq_33479841/article/details/134001410