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(垃圾回收)

 

 

总结

  1. java中的线程分为用户线程守护线程
  2. 程序中的所有的用户线程结束之后,不管守护线程处于什么状态,java虚拟机都会自动退出
  3. 调用线程的实例方法setDaemon()来设置线程是否是守护线程
  4. setDaemon()方法必须在线程的start()方法之前调用,在后面调用会报异常,并且不起效
  5. 线程的daemon默认值和其父线程一样

 

posted @ 2022-03-09 10:02  r1-12king  阅读(212)  评论(0编辑  收藏  举报