当前位置:

首页 > 编程开发 > Java中用户线程与守护线程如何使用

Java中用户线程与守护线程如何使用

1.默认用户线程Java语言中无论是线程还是线程池,默认都是用户线程,因此用户线程也被成为普通线程。以线程为例,想要查看线程是否为守护线程只需通过调用isDaemon()方法查询即可,如果查询的值为false则表示不为守护线程,自然也就属于用户线程了,如下代码所示:publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(newRunnable(){@Overridepublicvoidrun()

本文目录

      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());
      }

      以上程序的执行结果为: 

      Java中用户线程与守护线程如何使用

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

      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());
       }

      以上程序的执行结果为: 

      Java中用户线程与守护线程如何使用

      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;
          }
      });

      如下图所示: 

      Java中用户线程与守护线程如何使用

      如上图所示,可以看出,整个程序中有 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();
          }
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

       从上述结果可以看出,当程序执行完 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();
          }
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

       从上述结果可以看出,当线程设置为守护线程之后,整个程序不会等守护线程 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);
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

       从上述结果可以看出,当我们将 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);
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

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

      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());
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

       通过上述结果我们可以看出,即使是守护线程,当程序中调用 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();
          }
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

       通过上述结果可以看出,线程的类型不管是守护线程还是用户线程对程序执行的优先级是没有任何影响的,而当我们将 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();
          }
      }

      以上程序执行结果如下: 

      Java中用户线程与守护线程如何使用

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

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Java
      相关文章 更多
      Java单元测试入门:使用JUnit 5编写第一个测试用例
      Java单元测试入门:使用JUnit 5编写第一个测试用例

      面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

      Java变量未初始化报错解析:局部变量与成员变量默认值差异
      Java变量未初始化报错解析:局部变量与成员变量默认值差异

      深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

      详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

      Java中final与static final常量的定义及初始化规则
      Java中final与static final常量的定义及初始化规则

      深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

      Java抽象方法与接口方法的区别及选型建议
      Java抽象方法与接口方法的区别及选型建议

      深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

      Java实现HTTP请求与JSON解析的完整代码示例
      Java实现HTTP请求与JSON解析的完整代码示例

      本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

      Java递归出现StackOverflowError的原因分析及栈空间优化方案
      Java递归出现StackOverflowError的原因分析及栈空间优化方案

      详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

      Java空指针异常(NPE)的代码排查与修复实例
      Java空指针异常(NPE)的代码排查与修复实例

      针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

      selenium上传文件教程本地文件路径设置方法
      selenium上传文件教程本地文件路径设置方法

      深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

      针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。