Java中用户线程与守护线程的使用区别

 更新时间:2022年05月19日 16:29:55   作者:​ Java中文社群   ​  
这篇文章主要介绍了Java中用户线程与守护线程的使用区别,Java语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被成为普通线程,下文关于其与守护线程的区别详情,需要的小伙伴可以参考一下

前言;

在 Java 语言中线程分为两类:用户线程和守护线程,而二者之间的区别却鲜有人知,所以本文磊哥带你来看二者之间的区别,以及守护线程需要注意的一些事项。

1.默认用户线程

Java 语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被成为普通线程。

以线程为例,想要查看线程是否为守护线程只需通过调用 isDaemon() 方法查询即可,如果查询的值为 false 则表示不为守护线程,自然也就属于用户线程了,

如下代码所示:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是子线程");
        }
    });
    System.out.println("子线程==守护线程:" + thread.isDaemon());
    System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
}

以上程序的执行结果为: 

 从上述结果可以看出,默认情况下主线程和创建的新线程都为用户线程

PS:Thread.currentThread() 的意思是获取执行当前代码的线程实例。

2.主动修改为守护线程

守护线程(Daemon Thread)也被称之为后台线程或服务线程,守护线程是为用户线程服务的,当程序中的用户线程全部执行结束之后,守护线程也会跟随结束。

守护线程的角色就像“服务员”,而用户线程的角色就像“顾客”,当“顾客”全部走了之后(全部执行结束),那“服务员”(守护线程)也就没有了存在的意义,所以当一个程序中的全部用户线程都结束执行之后,那么无论守护线程是否还在工作都会随着用户线程一块结束,整个程序也会随之结束运行。

那如何将默认的用户线程修改为守护线程呢?

这个问题要分为两种情况来回答,首先如果是线程,则可以通过设置 setDaemon(true) 方法将用户线程直接修改为守护线程,而如果是线程池则需要通过 ThreadFactory 将线程池中的每个线程都为守护线程才行,接下来我们分别来实现一下。

2.1 设置线程为守护线程

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

 public static void main(String[] args) throws InterruptedException {
     Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
             System.out.println("我是子线程");
         }
     });
     // 设置子线程为守护线程
     thread.setDaemon(true);
     System.out.println("子线程==守护线程:" + thread.isDaemon());
     System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
 }

以上程序的执行结果为: 

2.2 设置线程池为守护线程

要把线程池设置为守护线程相对来说麻烦一些,需要将线程池中的所有线程都设置成守护线程,这个时候就需要使用 ThreadFactory 来定义线程池中每个线程的线程类型了,具体实现代码如下:

// 创建固定个数的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        // 设置线程为守护线程
        t.setDaemon(false);
        return t;
    }
});

如下图所示: 

如上图所示,可以看出,整个程序中有 10 个守护线程都是我创建的。其他几种创建线程池的设置方式类似,都是通过 ThreadFactory 统一设置的,这里就不一一列举了。

3.守护线程 VS 用户线程

通过前面的学习我们可以创建两种不同的线程类型了,那二者有什么差异呢?接下来我们使用一个小示例来看一下。

下面我们创建一个线程,分别将这个线程设置为用户线程和守护线程,在每个线程中执行一个 for 循环,总共执行 10 次信息打印,每次打印之后休眠 100 毫秒,来观察程序的运行结果。

3.1 用户线程

新建的线程默认就是用户线程,因此我们无需对线程进行任何特殊的处理,执行 for 循环即可(总共执行 10 次信息打印,每次打印之后休眠 100 毫秒),实现代码如下:

/**
 * Author:Java中文社群
 */
public class DaemonExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    // 打印 i 信息
                    System.out.println("i:" + i);
                    try {
                        // 休眠 100 毫秒
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 启动线程
        thread.start();
    }
}

以上程序执行结果如下: 

 从上述结果可以看出,当程序执行完 10 次打印之后才会正常结束进程。

3.2 守护线程

/**
 * Author:Java中文社群
 */
public class DaemonExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    // 打印 i 信息
                    System.out.println("i:" + i);
                    try {
                        // 休眠 100 毫秒
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 设置为守护线程
        thread.setDaemon(true);
        // 启动线程
        thread.start();
    }
}

以上程序执行结果如下: 

 从上述结果可以看出,当线程设置为守护线程之后,整个程序不会等守护线程 for 循环 10 次之后再进行关闭,而是当主线程结束之后,守护线程只执行了一次循环就结束运行了,由此可以看出守护线程和用户线程的不同。

3.3 小结

守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行完成之后程序就会结束运行,程序结束运行时不会管守护线程是否正在运行,由此我们可以看出守护线程在 Java 体系中权重是比较低的。

4.守护线程注意事项

守护线程的使用需要注意以下三个问题:

  • 守护线程的设置 setDaemon(true) 必须要放在线程的 start() 之前,否则程序会报错。
  • 在守护线程中创建的所有子线程都是守护线程。
  • 使用 jojn() 方法会等待一个线程执行完,无论此线程是用户线程还是守护线程。

接下来我们分别演示一下,以上的注意事项。

4.1 setDaemon 执行顺序

当我们将 setDaemon(true) 设置在 start() 之后,如下代码所示:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 1; i <= 10; i++) {
                // 打印 i 信息
                System.out.println("i:" + i + ",isDaemon:" +
                            Thread.currentThread().isDaemon());
                try {
                    // 休眠 100 毫秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    // 启动线程
    thread.start();
    // 设置为守护线程
    thread.setDaemon(true);
}

以上程序执行结果如下: 

 从上述结果可以看出,当我们将 setDaemon(true) 设置在 start() 之后,不但程序的执行会报错,而且设置的守护线程也不会生效。

4.2 守护线程的子线程

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {

                }
            });
            System.out.println("守护线程的子线程 thread2 isDaemon:" +
                              thread2.isDaemon());
        }
    });
    // 设置为守护线程
    thread.setDaemon(true);
    // 启动线程
    thread.start();

    Thread.sleep(1000);
}

以上程序执行结果如下: 

 从上述结果可以看出,守护线程中创建的子线程,默认情况下也属于守护线程

4.3 join 与守护线程

通过 3.2 部分的内容我们可以看出,默认情况下程序结束并不会等待守护线程执行完,而当我们调用线程的等待方法 join() 时,执行的结果就会和 3.2 的结果有所不同,下面我们一起来看吧,

示例代码如下:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 1; i <= 10; i++) {
                // 打印 i 信息
                System.out.println("i:" + i);
                try {
                    // 休眠 100 毫秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    // 设置为守护线程
    thread.setDaemon(true);
    // 启动线程
    thread.start();
    // 等待线程执行完
    thread.join();
    System.out.println("子线程==守护线程:" + thread.isDaemon());
    System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon());
}

以上程序执行结果如下: 

 通过上述结果我们可以看出,即使是守护线程,当程序中调用 join() 方法时,程序依然会等待守护线程执行完成之后再结束进程。

5.守护线程应用场景

守护线程的典型应用场景就是垃圾回收线程,当然还有一些场景也非常适合使用守护线程,比如服务器端的健康检测功能,对于一个服务器来说健康检测功能属于非核心非主流的服务业务,像这种为了主要业务服务的业务功能就非常合适使用守护线程,当程序中的主要业务都执行完成之后,服务业务也会跟随者一起销毁。

6.守护线程的执行优先级

首先来说,线程的类型(用户线程或守护线程)并不影响线程执行的优先级,如下代码所示,定义一个用户线程和守护线程,分别执行 10 万次循环,通过观察最后的打印结果来确认线程类型对程序执行优先级的影响。

public class DaemonExample {
    private static final int count = 100000;
    public static void main(String[] args) throws InterruptedException {
        // 定义任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < count; i++) {
                    System.out.println("执行线程:" + Thread.currentThread().getName());
                }
            }
        };
        // 创建守护线程 t1
        Thread t1 = new Thread(runnable, "t1");
        // 设置为守护线程
        t1.setDaemon(true);
        // 启动线程
        t1.start();
        // 创建用户线程 t2
        Thread t2 = new Thread(runnable, "t2");
        // 启动线程
        t2.start();
    }
}

以上程序执行结果如下: 

 通过上述结果可以看出,线程的类型不管是守护线程还是用户线程对程序执行的优先级是没有任何影响的,而当我们将 t2 的优先级调整为最大时,整个程序的运行结果就完全不同了,

如下代码所示:

public class DaemonExample {
    private static final int count = 100000;
    public static void main(String[] args) throws InterruptedException {
        // 定义任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < count; i++) {
                    System.out.println("执行线程:" + Thread.currentThread().getName());
                }
            }
        };
        // 创建守护线程 t1
        Thread t1 = new Thread(runnable, "t1");
        // 设置为守护线程
        t1.setDaemon(true);
        // 启动线程
        t1.start();
        // 创建用户线程 t2
        Thread t2 = new Thread(runnable, "t2");
        // 设置 t2 的优先级为最高
        t2.setPriority(Thread.MAX_PRIORITY);
        // 启动线程
        t2.start();
    }
}

以上程序执行结果如下: 

00000000 通过上述的结果可以看出,程序的类型和程序执行的优先级是没有任何关系,当新创建的线程默认的优先级都是 5 时,无论是守护线程还是用户线程,它们执行的优先级都是相同的,当将二者的优先级设置不同时,执行的结果也会随之改变(优先级设置的越高,最早被执行的概率也越大)。

7.总结

在 Java 语言中线程分为用户线程和守护线程,守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都结束之后,无论守护线程是否在工作都会跟随用户线程一起结束。守护线程从业务逻辑层面来看权重比较低,但对于线程调度器来说无论是守护线程还是用户线程,在优先级相同的情况下被执行的概率都是相同的。守护线程的经典使用场景是垃圾回收线程,守护线程中创建的线程默认情况下也都是守护线程。

到此这篇关于Java中用户线程与守护线程的使用区别的文章就介绍到这了,更多相关Java线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringCloud中的服务接口(api)

    SpringCloud中的服务接口(api)

    这篇文章主要介绍了SpringCloud中的服务接口(api),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • InputStream数据结构示例解析

    InputStream数据结构示例解析

    这篇文章主要为大家介绍了InputStream数据结构示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Spring指定bean在哪个应用加载(示例详解)

    Spring指定bean在哪个应用加载(示例详解)

    本文通过实例代码介绍了Spring指定bean在哪个应用加载,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-08-08
  • SpringMVC中的常用注解源码及解析

    SpringMVC中的常用注解源码及解析

    这篇文章主要介绍了SpringMVC中的常用注解源码及解析,SpringMVC是一种基于Java的Web框架,它使用了许多注解来简化开发过程,本文列举了一些常用的注解,需要的朋友可以参考下
    2023-10-10
  • SpringBoot依赖和代码分开打包的实现步骤

    SpringBoot依赖和代码分开打包的实现步骤

    本文主要介绍了SpringBoot依赖和代码分开打包的实现步骤,,这种方法将依赖和代码分开打包,一般更新只有代码修改,Pom文件是不会经常改动的,感兴趣的可以了解一下
    2023-10-10
  • Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)

    Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)

    这篇文章主要介绍了Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析,结合实例形式较为详细的分析说明了jdk1.4,jdk1.5和jdk1.6版本的使用区别,需要的朋友可以参考下
    2015-12-12
  • Mybatisplus详解如何注入自定义的SQL

    Mybatisplus详解如何注入自定义的SQL

    mybatis-plus 提供了许多默认单表 CRUD 语句,对于其他 SQL 情况爱莫能助。如果有一个删库跑路,并且需要多次调用,来清空多张表数据得需求,那么如何把他封装在 mybatis-plus 中调用呢,下面我们一起来看一下
    2022-06-06
  • 基于java内部类作用的深入分析

    基于java内部类作用的深入分析

    本篇文章介绍了,基于java内部类作用的深入分析。需要的朋友参考下
    2013-05-05
  • 带你入门Java的集合

    带你入门Java的集合

    Java的集合类型都是对java.util包中Collection接口的继承,这里我们主要介绍依赖于collection的一些主分支,一起来看一下Java中的collection集合类型总结
    2021-07-07
  • MyBatis-plus+达梦数据库实现自动生成代码的示例

    MyBatis-plus+达梦数据库实现自动生成代码的示例

    这篇文章主要介绍了MyBatis-plus+达梦数据库实现自动生成代码的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08

最新评论