文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中用户线程和守护线程区别这么大?

2024-12-03 05:37

关注

本文转载自微信公众号「Java中文社群」,作者磊哥。转载本文请联系Java中文社群公众号。

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

1.默认用户线程

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

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

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

以上程序的执行结果为:

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

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

2.主动修改为守护线程

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

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

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

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

2.1 设置线程为守护线程

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

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

以上程序的执行结果为:

2.2 设置线程池为守护线程

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

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

如下图所示:

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

3.守护线程 VS 用户线程

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

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

3.1 用户线程

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

  1.  
  2. public class DaemonExample { 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         Thread thread = new Thread(new Runnable() { 
  5.             @Override 
  6.             public void run() { 
  7.                 for (int i = 1; i <= 10; i++) { 
  8.                     // 打印 i 信息 
  9.                     System.out.println("i:" + i); 
  10.                     try { 
  11.                         // 休眠 100 毫秒 
  12.                         Thread.sleep(100); 
  13.                     } catch (InterruptedException e) { 
  14.                         e.printStackTrace(); 
  15.                     } 
  16.                 } 
  17.             } 
  18.         }); 
  19.         // 启动线程 
  20.         thread.start(); 
  21.     } 

以上程序执行结果如下:

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

3.2 守护线程

  1.  
  2. public class DaemonExample { 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         Thread thread = new Thread(new Runnable() { 
  5.             @Override 
  6.             public void run() { 
  7.                 for (int i = 1; i <= 10; i++) { 
  8.                     // 打印 i 信息 
  9.                     System.out.println("i:" + i); 
  10.                     try { 
  11.                         // 休眠 100 毫秒 
  12.                         Thread.sleep(100); 
  13.                     } catch (InterruptedException e) { 
  14.                         e.printStackTrace(); 
  15.                     } 
  16.                 } 
  17.             } 
  18.         }); 
  19.         // 设置为守护线程 
  20.         thread.setDaemon(true); 
  21.         // 启动线程 
  22.         thread.start(); 
  23.     } 

以上程序执行结果如下:

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

3.3 小结

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

4.守护线程注意事项

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

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

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

4.1 setDaemon 执行顺序

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

  1. public static void main(String[] args) throws InterruptedException { 
  2.     Thread thread = new Thread(new Runnable() { 
  3.         @Override 
  4.         public void run() { 
  5.             for (int i = 1; i <= 10; i++) { 
  6.                 // 打印 i 信息 
  7.                 System.out.println("i:" + i + ",isDaemon:" + 
  8.                             Thread.currentThread().isDaemon()); 
  9.                 try { 
  10.                     // 休眠 100 毫秒 
  11.                     Thread.sleep(100); 
  12.                 } catch (InterruptedException e) { 
  13.                     e.printStackTrace(); 
  14.                 } 
  15.             } 
  16.         } 
  17.     }); 
  18.     // 启动线程 
  19.     thread.start(); 
  20.     // 设置为守护线程 
  21.     thread.setDaemon(true); 

以上程序执行结果如下:

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

4.2 守护线程的子线程

  1. public static void main(String[] args) throws InterruptedException { 
  2.     Thread thread = new Thread(new Runnable() { 
  3.         @Override 
  4.         public void run() { 
  5.             Thread thread2 = new Thread(new Runnable() { 
  6.                 @Override 
  7.                 public void run() { 
  8.  
  9.                 } 
  10.             }); 
  11.             System.out.println("守护线程的子线程 thread2 isDaemon:" + 
  12.                                thread2.isDaemon()); 
  13.         } 
  14.     }); 
  15.     // 设置为守护线程 
  16.     thread.setDaemon(true); 
  17.     // 启动线程 
  18.     thread.start(); 
  19.  
  20.     Thread.sleep(1000); 

以上程序执行结果如下:

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

4.3 join 与守护线程

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

  1. public static void main(String[] args) throws InterruptedException { 
  2.     Thread thread = new Thread(new Runnable() { 
  3.         @Override 
  4.         public void run() { 
  5.             for (int i = 1; i <= 10; i++) { 
  6.                 // 打印 i 信息 
  7.                 System.out.println("i:" + i); 
  8.                 try { 
  9.                     // 休眠 100 毫秒 
  10.                     Thread.sleep(100); 
  11.                 } catch (InterruptedException e) { 
  12.                     e.printStackTrace(); 
  13.                 } 
  14.             } 
  15.         } 
  16.     }); 
  17.     // 设置为守护线程 
  18.     thread.setDaemon(true); 
  19.     // 启动线程 
  20.     thread.start(); 
  21.     // 等待线程执行完 
  22.     thread.join(); 
  23.     System.out.println("子线程==守护线程:" + thread.isDaemon()); 
  24.     System.out.println("主线程==守护线程:" + Thread.currentThread().isDaemon()); 

以上程序执行结果如下:

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

5.守护线程应用场景

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

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

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

  1. public class DaemonExample { 
  2.     private static final int count = 100000; 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 定义任务 
  5.         Runnable runnable = new Runnable() { 
  6.             @Override 
  7.             public void run() { 
  8.                 for (int i = 0; i < count; i++) { 
  9.                     System.out.println("执行线程:" + Thread.currentThread().getName()); 
  10.                 } 
  11.             } 
  12.         }; 
  13.         // 创建守护线程 t1 
  14.         Thread t1 = new Thread(runnable, "t1"); 
  15.         // 设置为守护线程 
  16.         t1.setDaemon(true); 
  17.         // 启动线程 
  18.         t1.start(); 
  19.         // 创建用户线程 t2 
  20.         Thread t2 = new Thread(runnable, "t2"); 
  21.         // 启动线程 
  22.         t2.start(); 
  23.     } 

以上程序执行结果如下:

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

  1. public class DaemonExample { 
  2.     private static final int count = 100000; 
  3.     public static void main(String[] args) throws InterruptedException { 
  4.         // 定义任务 
  5.         Runnable runnable = new Runnable() { 
  6.             @Override 
  7.             public void run() { 
  8.                 for (int i = 0; i < count; i++) { 
  9.                     System.out.println("执行线程:" + Thread.currentThread().getName()); 
  10.                 } 
  11.             } 
  12.         }; 
  13.         // 创建守护线程 t1 
  14.         Thread t1 = new Thread(runnable, "t1"); 
  15.         // 设置为守护线程 
  16.         t1.setDaemon(true); 
  17.         // 启动线程 
  18.         t1.start(); 
  19.         // 创建用户线程 t2 
  20.         Thread t2 = new Thread(runnable, "t2"); 
  21.         // 设置 t2 的优先级为最高 
  22.         t2.setPriority(Thread.MAX_PRIORITY); 
  23.         // 启动线程 
  24.         t2.start(); 
  25.     } 

以上程序执行结果如下:

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

7.总结

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

 

来源:Java中文社群内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯