Java 用户线程和守护线程
Java中的线程
Java中存在两种线程
- 用户线程
- 守护线程
用户线程:常用的普通线程均是用户线程
守护线程:指在程序运行的时候在后台提供一种通用服务的线程,守护线程是为用户线程服务的。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。
- 通过Thread.setDaemon(false)设置为用户线程;
- 通过Thread.setDaemon(true)设置为守护线程。
- 如果不设置次属性,默认为用户线程。
- 这个函数务必在线程启动前进行调用,否则会报java.lang.IllegalThreadStateException异常,启动的线程无法变成守护线程,而是用户线程。
用户线程和守护线程的区别
1. 主线程结束后用户线程还会继续运行,JVM存活;主线程结束后守护线程和JVM的状态又下面第2条确定。
2.如果JVM中所有的线程都是守护线程,那么JVM就会退出,进而守护线程也会退出。如果JVM中还存在用户线程,那么JVM就会一直存活,不会退出。
ps: 如果JAVA虚拟机是被强制停止的,例如使用kill 命令,那么即使用户线程也无法阻止JAVA虚拟机的停止。
由此可以得到:
守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
用户线程是独立存在的,不会因为其他用户线程退出而退出。
守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程都是守护线程。与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出。
测试代码
场景一:程序只有守护线程时,系统会自动退出,反之不会
1 public class Demo1 { 2 3 public static class T1 extends Thread { 4 public T1(String name) { 5 super(name); 6 } 7 8 @Override 9 public void run() { 10 System.out.println(this.getName() + "开始执行," + (this.isDaemon() ? "我是守护线程" : "我是用户线程")); 11 while (true) ; 12 } 13 } 14 15 public static void main(String[] args) { 16 T1 t1 = new T1("子线程1"); 17 t1.start(); 18 System.out.println("主线程结束"); 19 } 20 }
结果如图:
可以看到主线程已经结束了,但是程序无法退出,原因:子线程1是用户线程,内部有个死循环,一直处于运行状态,无法结束。
再看下面的代码:
1 public class Demo2 { 2 3 public static class T1 extends Thread { 4 public T1(String name) { 5 super(name); 6 } 7 8 @Override 9 public void run() { 10 System.out.println(this.getName() + "开始执行," + (this.isDaemon() ? "我是守护线程" : "我是用户线程")); 11 while (true) { 12 } 13 } 14 } 15 16 public static void main(String[] args) { 17 T1 t1 = new T1("子线程1"); 18 t1.setDaemon(true); 19 t1.start(); 20 System.out.println("主线程结束"); 21 } 22 }
结果如图:
程序可以正常结束了,代码中通过t1.setDaemon(true);
将t1线程设置为守护线程,main方法所在的主线程执行完毕之后,程序就退出了。
结论:当程序中所有的用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出。
场景二:设置守护线程,需要在start()方法之前进行
代码如下:
1 import java.util.concurrent.TimeUnit; 2 3 public class Demo3 { 4 5 public static void main(String[] args) { 6 Thread t1 = new Thread() { 7 @Override 8 public void run() { 9 try { 10 TimeUnit.SECONDS.sleep(10); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 }; 16 t1.start(); 17 t1.setDaemon(true); 18 } 19 }
结果如下:
t1.setDaemon(true);
是在t1的start()方法之后执行的,执行会报异常。
场景三、线程daemon的默认值
我们看一下创建线程源码,位于Thread类的init()方法中:
1 Thread parent = currentThread(); 2 this.daemon = parent.isDaemon();
dameon的默认值为为父线程的daemon,也就是说,父线程如果为用户线程,子线程默认也是用户现场,父线程如果是守护线程,子线程默认也是守护线程。
示例代码:
1 import java.util.concurrent.TimeUnit; 2 3 public class Demo4 { 4 public static class T1 extends Thread { 5 public T1(String name) { 6 super(name); 7 } 8 9 @Override 10 public void run() { 11 System.out.println(this.getName() + ".daemon:" + this.isDaemon()); 12 } 13 } 14 15 public static void main(String[] args) throws InterruptedException { 16 17 System.out.println(Thread.currentThread().getName() + ".daemon:" + Thread.currentThread().isDaemon()); 18 19 T1 t1 = new T1("t1"); 20 t1.start(); 21 22 Thread t2 = new Thread() { 23 @Override 24 public void run() { 25 System.out.println(this.getName() + ".daemon:" + this.isDaemon()); 26 T1 t3 = new T1("t3"); 27 t3.start(); 28 } 29 }; 30 31 t2.setName("t2"); 32 t2.setDaemon(true); 33 t2.start(); 34 35 TimeUnit.SECONDS.sleep(2); 36 } 37 }
运行代码,输出:
t1是由主线程(main方法所在的线程)创建的,main线程是t1的父线程,所以t1.daemon为false,说明t1是用户线程。
t2线程调用了setDaemon(true);
将其设为守护线程,t3是由t2创建的,所以t3默认线程类型和t2一样,t2.daemon为true。
守护线程适用场景
针对于守护线程的特点,java 守护线程通常可用于开发一些为其它用户线程服务的功能。比如说心跳检测,事件监听等。Java 中最有名的守护进程当属GC(垃圾回收)
总结
- java中的线程分为用户线程和守护线程
- 程序中的所有的用户线程结束之后,不管守护线程处于什么状态,java虚拟机都会自动退出
- 调用线程的实例方法setDaemon()来设置线程是否是守护线程
- setDaemon()方法必须在线程的start()方法之前调用,在后面调用会报异常,并且不起效
- 线程的daemon默认值和其父线程一样
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战