Java中用戶線程與守護線程的使用區(qū)別
前言;
在 Java 語言中線程分為兩類:用戶線程和守護線程,而二者之間的區(qū)別卻鮮有人知,所以本文磊哥帶你來看二者之間的區(qū)別,以及守護線程需要注意的一些事項。
1.默認用戶線程
Java 語言中無論是線程還是線程池,默認都是用戶線程,因此用戶線程也被成為普通線程。
以線程為例,想要查看線程是否為守護線程只需通過調用 isDaemon() 方法查詢即可,如果查詢的值為 false 則表示不為守護線程,自然也就屬于用戶線程了,
如下代碼所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是子線程");
}
});
System.out.println("子線程==守護線程:" + thread.isDaemon());
System.out.println("主線程==守護線程:" + Thread.currentThread().isDaemon());
}以上程序的執(zhí)行結果為:

從上述結果可以看出,默認情況下主線程和創(chuàng)建的新線程都為用戶線程。
PS:Thread.currentThread() 的意思是獲取執(zhí)行當前代碼的線程實例。
2.主動修改為守護線程
守護線程(Daemon Thread)也被稱之為后臺線程或服務線程,守護線程是為用戶線程服務的,當程序中的用戶線程全部執(zhí)行結束之后,守護線程也會跟隨結束。
守護線程的角色就像“服務員”,而用戶線程的角色就像“顧客”,當“顧客”全部走了之后(全部執(zhí)行結束),那“服務員”(守護線程)也就沒有了存在的意義,所以當一個程序中的全部用戶線程都結束執(zhí)行之后,那么無論守護線程是否還在工作都會隨著用戶線程一塊結束,整個程序也會隨之結束運行。
那如何將默認的用戶線程修改為守護線程呢?
這個問題要分為兩種情況來回答,首先如果是線程,則可以通過設置 setDaemon(true) 方法將用戶線程直接修改為守護線程,而如果是線程池則需要通過 ThreadFactory 將線程池中的每個線程都為守護線程才行,接下來我們分別來實現(xiàn)一下。
2.1 設置線程為守護線程
如果使用的是線程,可以通過 setDaemon(true) 方法將線程類型更改為守護線程,如下代碼所示:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是子線程");
}
});
// 設置子線程為守護線程
thread.setDaemon(true);
System.out.println("子線程==守護線程:" + thread.isDaemon());
System.out.println("主線程==守護線程:" + Thread.currentThread().isDaemon());
}以上程序的執(zhí)行結果為:

2.2 設置線程池為守護線程
要把線程池設置為守護線程相對來說麻煩一些,需要將線程池中的所有線程都設置成守護線程,這個時候就需要使用 ThreadFactory 來定義線程池中每個線程的線程類型了,具體實現(xiàn)代碼如下:
// 創(chuàng)建固定個數(shù)的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
// 設置線程為守護線程
t.setDaemon(false);
return t;
}
});如下圖所示:

如上圖所示,可以看出,整個程序中有 10 個守護線程都是我創(chuàng)建的。其他幾種創(chuàng)建線程池的設置方式類似,都是通過 ThreadFactory 統(tǒng)一設置的,這里就不一一列舉了。
3.守護線程 VS 用戶線程
通過前面的學習我們可以創(chuàng)建兩種不同的線程類型了,那二者有什么差異呢?接下來我們使用一個小示例來看一下。
下面我們創(chuàng)建一個線程,分別將這個線程設置為用戶線程和守護線程,在每個線程中執(zhí)行一個 for 循環(huán),總共執(zhí)行 10 次信息打印,每次打印之后休眠 100 毫秒,來觀察程序的運行結果。
3.1 用戶線程
新建的線程默認就是用戶線程,因此我們無需對線程進行任何特殊的處理,執(zhí)行 for 循環(huán)即可(總共執(zhí)行 10 次信息打印,每次打印之后休眠 100 毫秒),實現(xiàn)代碼如下:
/**
* Author:Java中文社群
*/
public class DaemonExample {
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();
}
}以上程序執(zhí)行結果如下:

從上述結果可以看出,當程序執(zhí)行完 10 次打印之后才會正常結束進程。
3.2 守護線程
/**
* Author:Java中文社群
*/
public class DaemonExample {
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();
}
}以上程序執(zhí)行結果如下:

從上述結果可以看出,當線程設置為守護線程之后,整個程序不會等守護線程 for 循環(huán) 10 次之后再進行關閉,而是當主線程結束之后,守護線程只執(zhí)行了一次循環(huán)就結束運行了,由此可以看出守護線程和用戶線程的不同。
3.3 小結
守護線程是為用戶線程服務的,當一個程序中的所有用戶線程都執(zhí)行完成之后程序就會結束運行,程序結束運行時不會管守護線程是否正在運行,由此我們可以看出守護線程在 Java 體系中權重是比較低的。
4.守護線程注意事項
守護線程的使用需要注意以下三個問題:
- 守護線程的設置
setDaemon(true)必須要放在線程的start()之前,否則程序會報錯。 - 在守護線程中創(chuàng)建的所有子線程都是守護線程。
- 使用
jojn()方法會等待一個線程執(zhí)行完,無論此線程是用戶線程還是守護線程。
接下來我們分別演示一下,以上的注意事項。
4.1 setDaemon 執(zhí)行順序
當我們將 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);
}以上程序執(zhí)行結果如下:

從上述結果可以看出,當我們將 setDaemon(true) 設置在 start() 之后,不但程序的執(zhí)行會報錯,而且設置的守護線程也不會生效。
4.2 守護線程的子線程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
}
});
System.out.println("守護線程的子線程 thread2 isDaemon:" +
thread2.isDaemon());
}
});
// 設置為守護線程
thread.setDaemon(true);
// 啟動線程
thread.start();
Thread.sleep(1000);
}以上程序執(zhí)行結果如下:

從上述結果可以看出,守護線程中創(chuàng)建的子線程,默認情況下也屬于守護線程。
4.3 join 與守護線程
通過 3.2 部分的內容我們可以看出,默認情況下程序結束并不會等待守護線程執(zhí)行完,而當我們調用線程的等待方法 join() 時,執(zhí)行的結果就會和 3.2 的結果有所不同,下面我們一起來看吧,
示例代碼如下:
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();
// 等待線程執(zhí)行完
thread.join();
System.out.println("子線程==守護線程:" + thread.isDaemon());
System.out.println("主線程==守護線程:" + Thread.currentThread().isDaemon());
}以上程序執(zhí)行結果如下:

通過上述結果我們可以看出,即使是守護線程,當程序中調用 join() 方法時,程序依然會等待守護線程執(zhí)行完成之后再結束進程。
5.守護線程應用場景
守護線程的典型應用場景就是垃圾回收線程,當然還有一些場景也非常適合使用守護線程,比如服務器端的健康檢測功能,對于一個服務器來說健康檢測功能屬于非核心非主流的服務業(yè)務,像這種為了主要業(yè)務服務的業(yè)務功能就非常合適使用守護線程,當程序中的主要業(yè)務都執(zhí)行完成之后,服務業(yè)務也會跟隨者一起銷毀。
6.守護線程的執(zhí)行優(yōu)先級
首先來說,線程的類型(用戶線程或守護線程)并不影響線程執(zhí)行的優(yōu)先級,如下代碼所示,定義一個用戶線程和守護線程,分別執(zhí)行 10 萬次循環(huán),通過觀察最后的打印結果來確認線程類型對程序執(zhí)行優(yōu)先級的影響。
public class DaemonExample {
private static final int count = 100000;
public static void main(String[] args) throws InterruptedException {
// 定義任務
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("執(zhí)行線程:" + Thread.currentThread().getName());
}
}
};
// 創(chuàng)建守護線程 t1
Thread t1 = new Thread(runnable, "t1");
// 設置為守護線程
t1.setDaemon(true);
// 啟動線程
t1.start();
// 創(chuàng)建用戶線程 t2
Thread t2 = new Thread(runnable, "t2");
// 啟動線程
t2.start();
}
}以上程序執(zhí)行結果如下:

通過上述結果可以看出,線程的類型不管是守護線程還是用戶線程對程序執(zhí)行的優(yōu)先級是沒有任何影響的,而當我們將 t2 的優(yōu)先級調整為最大時,整個程序的運行結果就完全不同了,
如下代碼所示:
public class DaemonExample {
private static final int count = 100000;
public static void main(String[] args) throws InterruptedException {
// 定義任務
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("執(zhí)行線程:" + Thread.currentThread().getName());
}
}
};
// 創(chuàng)建守護線程 t1
Thread t1 = new Thread(runnable, "t1");
// 設置為守護線程
t1.setDaemon(true);
// 啟動線程
t1.start();
// 創(chuàng)建用戶線程 t2
Thread t2 = new Thread(runnable, "t2");
// 設置 t2 的優(yōu)先級為最高
t2.setPriority(Thread.MAX_PRIORITY);
// 啟動線程
t2.start();
}
}以上程序執(zhí)行結果如下:

00000000 通過上述的結果可以看出,程序的類型和程序執(zhí)行的優(yōu)先級是沒有任何關系,當新創(chuàng)建的線程默認的優(yōu)先級都是 5 時,無論是守護線程還是用戶線程,它們執(zhí)行的優(yōu)先級都是相同的,當將二者的優(yōu)先級設置不同時,執(zhí)行的結果也會隨之改變(優(yōu)先級設置的越高,最早被執(zhí)行的概率也越大)。
7.總結
在 Java 語言中線程分為用戶線程和守護線程,守護線程是用來為用戶線程服務的,當一個程序中的所有用戶線程都結束之后,無論守護線程是否在工作都會跟隨用戶線程一起結束。守護線程從業(yè)務邏輯層面來看權重比較低,但對于線程調度器來說無論是守護線程還是用戶線程,在優(yōu)先級相同的情況下被執(zhí)行的概率都是相同的。守護線程的經典使用場景是垃圾回收線程,守護線程中創(chuàng)建的線程默認情況下也都是守護線程。
到此這篇關于Java中用戶線程與守護線程的使用區(qū)別的文章就介紹到這了,更多相關Java線程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot依賴和代碼分開打包的實現(xiàn)步驟
本文主要介紹了SpringBoot依賴和代碼分開打包的實現(xiàn)步驟,,這種方法將依賴和代碼分開打包,一般更新只有代碼修改,Pom文件是不會經常改動的,感興趣的可以了解一下2023-10-10
Java編程之jdk1.4,jdk1.5和jdk1.6的區(qū)別分析(經典)
這篇文章主要介紹了Java編程之jdk1.4,jdk1.5和jdk1.6的區(qū)別分析,結合實例形式較為詳細的分析說明了jdk1.4,jdk1.5和jdk1.6版本的使用區(qū)別,需要的朋友可以參考下2015-12-12
MyBatis-plus+達夢數(shù)據(jù)庫實現(xiàn)自動生成代碼的示例
這篇文章主要介紹了MyBatis-plus+達夢數(shù)據(jù)庫實現(xiàn)自動生成代碼的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08

