Java 使用者執行緒和守護執行緒
Java中的執行緒
Java中存在兩種執行緒
- 使用者執行緒
- 守護執行緒
使用者執行緒:常用的普通執行緒均是使用者執行緒
守護執行緒:指在程式執行的時候在後臺提供一種通用服務的執行緒,守護執行緒是為使用者執行緒服務的。也就是說守護執行緒不依賴於終端,但是依賴於系統,與系統“同生共死”。
- 通過Thread.setDaemon(false)設定為使用者執行緒;
- 通過Thread.setDaemon(true)設定為守護執行緒。
- 如果不設定次屬性,預設為使用者執行緒。
- 這個函式務必線上程啟動前進行呼叫,否則會報java.lang.IllegalThreadStateException異常,啟動的執行緒無法變成守護執行緒,而是使用者執行緒。
使用者執行緒和守護執行緒的區別
1. 主執行緒結束後用戶執行緒還會繼續執行,JVM存活;主執行緒結束後守護執行緒和JVM的狀態又下面第2條確定。
2.如果JVM中所有的執行緒都是守護執行緒,那麼JVM就會退出,進而守護執行緒也會退出。如果JVM中還存在使用者執行緒,那麼JVM就會一直存活,不會退出。
由此可以得到:
守護執行緒是依賴於使用者執行緒,使用者執行緒退出了,守護執行緒也就會退出,典型的守護執行緒如垃圾回收執行緒。
使用者執行緒是獨立存在的,不會因為其他使用者執行緒退出而退出。
守護執行緒是一種特殊的執行緒,在後臺默默地完成一些系統性的服務,比如垃圾回收執行緒
測試程式碼
場景一:程式只有守護執行緒時,系統會自動退出,反之不會
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預設值和其父執行緒一樣