在Java中通常有兩種線程:用戶線程和守護線程(也被稱為服務線程)
通過Thread.setDaemon(false)設置為用戶線程
通過Thread.setDaemon(true)設置為守護線程
線程屬性的設置要在線程啟動之前,否則會報IllegalThreadStateException異常
如果不設置線程屬性,那么默認為用戶線程
用戶線程和守護線程的區別:
1.主線程結束后用戶線程還會繼續運行,JVM存活
2.如果沒有用戶線程,都是守護線程,那么JVM結束(所有的線程都會結束)
守護進程(Daemon)是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴於終端,但是依賴於系統,與系統“同生共死”。那Java的守護線程是什么樣子的呢。當JVM中所有的線程都是守護線程的時候,JVM就可以退出了;如果還有一個或以上的非守護線程則JVM不會退出。
守護線程是一種特殊的線程,在后台默默地完成一些系統性的服務,比如垃圾回收線程、JIT線程都是守護線程。與之對應的是用戶線程,用戶線程可以理解為是系統的工作線程,它會完成這個程序需要完成的業務操作。如果用戶線程全部結束了,意味着程序需要完成的業務操作已經結束了,系統可以退出了。所以當系統只剩下守護進程的時候,java虛擬機會自動退出。
java線程分為用戶線程和守護線程,線程的daemon屬性為true表示是守護線程,false表示是用戶線程。
下面我們來看一下守護線程的一些特性。
程序只有守護線程時,系統會自動退出
public class Demo1 { public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(this.getName() + "開始執行," + (this.isDaemon() ? "我是守護線程" : "我是用戶線程")); while (true) ; } } public static void main(String[] args) { T1 t1 = new T1("子線程1"); t1.start(); System.out.println("主線程結束"); } }
運行上面代碼,結果如下:
可以看到主線程已經結束了,但是程序無法退出,原因:子線程1是用戶線程,內部有個死循環,一直處於運行狀態,無法結束。
再看下面的代碼:
public class Demo2 { public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(this.getName() + "開始執行," + (this.isDaemon() ? "我是守護線程" : "我是用戶線程")); while (true) ; } } public static void main(String[] args) { T1 t1 = new T1("子線程1"); t1.setDaemon(true); t1.start(); System.out.println("主線程結束"); } }
運行結果:
程序可以正常結束了,代碼中通過t1.setDaemon(true);
將t1線程設置為守護線程,main方法所在的主線程執行完畢之后,程序就退出了。
結論:當程序中所有的用戶線程執行完畢之后,不管守護線程是否結束,系統都會自動退出。
設置守護線程,需要在start()方法之前進行
import java.util.concurrent.TimeUnit; public class Demo3 { public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); t1.setDaemon(true); } }
t1.setDaemon(true);
是在t1的start()方法之后執行的,執行會報異常,運行結果如下:
線程daemon的默認值
我們看一下創建線程源碼,位於Thread類的init()方法中:
Thread parent = currentThread();
this.daemon = parent.isDaemon();
dameon的默認值為為父線程的daemon,也就是說,父線程如果為用戶線程,子線程默認也是用戶現場,父線程如果是守護線程,子線程默認也是守護線程。
示例代碼:
import java.util.concurrent.TimeUnit; public class Demo4 { public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(this.getName() + ".daemon:" + this.isDaemon()); } } public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName() + ".daemon:" + Thread.currentThread().isDaemon()); T1 t1 = new T1("t1"); t1.start(); Thread t2 = new Thread() { @Override public void run() { System.out.println(this.getName() + ".daemon:" + this.isDaemon()); T1 t3 = new T1("t3"); t3.start(); } }; t2.setName("t2"); t2.setDaemon(true); t2.start(); TimeUnit.SECONDS.sleep(2); } }
運行代碼,輸出:
main.daemon:false t1.daemon:false t2.daemon:true t3.daemon:true
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默認值和其父線程一樣