面試突擊31:什麼是守護執行緒?它和用戶執行緒有什麼區別?
在 Java 語言中,執行緒分為兩類:用戶執行緒和守護執行緒,默認情況下我們創建的執行緒或執行緒池都是用戶執行緒,所以用戶執行緒也被稱之為普通執行緒。
想要查看執行緒到底是用戶執行緒還是守護執行緒,可以通過 Thread.isDaemon() 方法來判斷,如果返回的結果是 true 則為守護執行緒,反之則為用戶執行緒。
我們來測試一下默認情況下執行緒和執行緒池屬於哪種執行緒類型?測試程式碼如下:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 執行緒類型:守護執行緒 OR 用戶執行緒
*/
public class ThreadType {
public static void main(String[] args) {
// 創建執行緒
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//...
}
});
// 創建執行緒池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10,
0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("ThreadPool 執行緒類型:" +
(Thread.currentThread().isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
}
});
System.out.println("Thread 執行緒類型:" +
(thread.isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
System.out.println("main 執行緒類型:" +
(Thread.currentThread().isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
}
}
以上程式的執行結果如下圖所示:

從上述結果可以看出,默認情況下創建的執行緒和執行緒池都是用戶執行緒。
守護執行緒定義
守護執行緒(Daemon Thread)也被稱之為後台執行緒或服務執行緒,守護執行緒是為用戶執行緒服務的,當程式中的用戶執行緒全部執行結束之後,守護執行緒也會跟隨結束。
守護執行緒的角色就像「服務員」,而用戶執行緒的角色就像「顧客」,當「顧客」全部走了之後(全部執行結束),那「服務員」(守護執行緒)也就沒有了存在的意義,所以當一個程式中的全部用戶執行緒都結束執行之後,那麼無論守護執行緒是否還在工作都會隨著用戶執行緒一塊結束,整個程式也會隨之結束運行。
創建守護執行緒
我們可以通過 Thread.setDaemon(true) 方法將執行緒設置為守護執行緒,比如以下程式碼的實現:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//...
}
});
// 設置執行緒為守護執行緒
thread.setDaemon(true);
System.out.println("Thread 執行緒類型:" +
(thread.isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
System.out.println("main 執行緒類型:" +
(Thread.currentThread().isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
}
以上程式的執行結果如下圖所示:

將執行緒池設置為守護執行緒
要把執行緒池設置為守護執行緒相對來說麻煩一些,需要將執行緒池中的所有執行緒都設置成守護執行緒,這個時候就需要使用執行緒工廠 ThreadFactory 來設置了(執行緒池中的所有執行緒都是通過執行緒工廠創建的),它的具體實現程式碼如下:
public static void main(String[] args) throws InterruptedException {
// 執行緒工廠(設置守護執行緒)
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
// 設置為守護執行緒
thread.setDaemon(true);
return thread;
}
};
// 創建執行緒池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10,
0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), threadFactory);
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("ThreadPool 執行緒類型:" +
(Thread.currentThread().isDaemon() == true ? "守護執行緒" : "用戶執行緒"));
}
});
Thread.sleep(2000);
}
以上程式的執行結果如下圖所示:

守護執行緒 VS 用戶執行緒
通過前面的內容我們了解了什麼是用戶執行緒和守護執行緒了,那二者有什麼區別呢?接下來我們用一個小示例來觀察一下。
接下來我們將創建一個執行緒,分別將這個執行緒設置為用戶執行緒和守護執行緒,在每個執行緒中執行一個 for 循環,總共執行 10 次資訊列印,每次列印之後休眠 100 毫秒,來觀察程式的運行結果。
用戶執行緒
新建的執行緒默認就是用戶執行緒,因此我們無需對執行緒進行任何特殊的處理,執行 for 循環即可(總共執行 10 次資訊列印,每次列印之後休眠 100 毫秒),實現程式碼如下:
public static void main(String[] args) throws InterruptedException {
// 創建用戶執行緒
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 列印 i 資訊
System.out.println("i:" + i);
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 啟動執行緒
thread.start();
}
以上程式的執行結果如下圖所示:

從上述結果可以看出,當程式執行完 10 次列印之後才會正常結束進程。
守護執行緒
public static void main(String[] args) throws InterruptedException {
// 創建守護執行緒
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 列印 i 資訊
System.out.println("i:" + i);
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 設置為守護執行緒
thread.setDaemon(true);
// 啟動執行緒
thread.start();
}
以上程式執行結果如下圖所示:

從上述結果可以看出,當執行緒設置為守護執行緒之後,整個程式不會等守護執行緒 for 循環 10 次之後再進行關閉,而是當主執行緒結束之後,守護執行緒一次循環都沒執行就結束了,由此可以看出守護執行緒和用戶執行緒的不同。
守護執行緒注意事項
守護執行緒的設置 setDaemon(true) 必須要放在執行緒的 start() 之前,否則程式會報錯。也就是說在運行執行緒之前,一定要先確定執行緒的類型,並且執行緒運行之後是不允許修改執行緒的類型的。
接下來我們來演示一下,如果在程式運行執行再設置執行緒的類型會出現什麼問題?演示程式碼如下:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
// 列印 i 資訊
System.out.println("i:" + i + ",isDaemon:" +
Thread.currentThread().isDaemon());
try {
// 休眠 100 毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 啟動執行緒
thread.start();
// 設置為守護執行緒
thread.setDaemon(true);
}
以上程式執行結果如下圖所示:

從上述結果可以看出,當我們將 setDaemon(true) 設置在 start() 之後,不但程式的執行會報錯,而且設置的守護執行緒也不會生效。
總結
在 Java 語言中執行緒分為兩類:用戶執行緒和守護執行緒,默認情況下我們創建的執行緒或執行緒池都是用戶執行緒,守護執行緒是為用戶執行緒服務的,當一個程式中的所有用戶執行緒都執行完成之後程式就會結束運行,程式結束運行時不會管守護執行緒是否正在運行,由此我們可以看出守護執行緒在 Java 體系中權重是比較低的,這就是守護執行緒和用戶執行緒的區別。
是非審之於己,毀譽聽之於人,得失安之於數。
公眾號:Java面試真題解析

