Java中用戶(hù)線(xiàn)程與守護(hù)線(xiàn)程的使用區(qū)別
前言;
在 Java 語(yǔ)言中線(xiàn)程分為兩類(lèi):用戶(hù)線(xiàn)程和守護(hù)線(xiàn)程,而二者之間的區(qū)別卻鮮有人知,所以本文磊哥帶你來(lái)看二者之間的區(qū)別,以及守護(hù)線(xiàn)程需要注意的一些事項(xiàng)。
1.默認(rèn)用戶(hù)線(xiàn)程
Java 語(yǔ)言中無(wú)論是線(xiàn)程還是線(xiàn)程池,默認(rèn)都是用戶(hù)線(xiàn)程,因此用戶(hù)線(xiàn)程也被成為普通線(xiàn)程。
以線(xiàn)程為例,想要查看線(xiàn)程是否為守護(hù)線(xiàn)程只需通過(guò)調(diào)用 isDaemon()
方法查詢(xún)即可,如果查詢(xún)的值為 false
則表示不為守護(hù)線(xiàn)程,自然也就屬于用戶(hù)線(xiàn)程了,
如下代碼所示:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("我是子線(xiàn)程"); } }); System.out.println("子線(xiàn)程==守護(hù)線(xiàn)程:" + thread.isDaemon()); System.out.println("主線(xiàn)程==守護(hù)線(xiàn)程:" + Thread.currentThread().isDaemon()); }
以上程序的執(zhí)行結(jié)果為:
從上述結(jié)果可以看出,默認(rèn)情況下主線(xiàn)程和創(chuàng)建的新線(xiàn)程都為用戶(hù)線(xiàn)程。
PS:Thread.currentThread() 的意思是獲取執(zhí)行當(dāng)前代碼的線(xiàn)程實(shí)例。
2.主動(dòng)修改為守護(hù)線(xiàn)程
守護(hù)線(xiàn)程(Daemon Thread)也被稱(chēng)之為后臺(tái)線(xiàn)程或服務(wù)線(xiàn)程,守護(hù)線(xiàn)程是為用戶(hù)線(xiàn)程服務(wù)的,當(dāng)程序中的用戶(hù)線(xiàn)程全部執(zhí)行結(jié)束之后,守護(hù)線(xiàn)程也會(huì)跟隨結(jié)束。
守護(hù)線(xiàn)程的角色就像“服務(wù)員”,而用戶(hù)線(xiàn)程的角色就像“顧客”,當(dāng)“顧客”全部走了之后(全部執(zhí)行結(jié)束),那“服務(wù)員”(守護(hù)線(xiàn)程)也就沒(méi)有了存在的意義,所以當(dāng)一個(gè)程序中的全部用戶(hù)線(xiàn)程都結(jié)束執(zhí)行之后,那么無(wú)論守護(hù)線(xiàn)程是否還在工作都會(huì)隨著用戶(hù)線(xiàn)程一塊結(jié)束,整個(gè)程序也會(huì)隨之結(jié)束運(yùn)行。
那如何將默認(rèn)的用戶(hù)線(xiàn)程修改為守護(hù)線(xiàn)程呢?
這個(gè)問(wèn)題要分為兩種情況來(lái)回答,首先如果是線(xiàn)程,則可以通過(guò)設(shè)置 setDaemon(true)
方法將用戶(hù)線(xiàn)程直接修改為守護(hù)線(xiàn)程,而如果是線(xiàn)程池則需要通過(guò) ThreadFactory
將線(xiàn)程池中的每個(gè)線(xiàn)程都為守護(hù)線(xiàn)程才行,接下來(lái)我們分別來(lái)實(shí)現(xiàn)一下。
2.1 設(shè)置線(xiàn)程為守護(hù)線(xiàn)程
如果使用的是線(xiàn)程,可以通過(guò) setDaemon(true)
方法將線(xiàn)程類(lèi)型更改為守護(hù)線(xiàn)程,如下代碼所示:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("我是子線(xiàn)程"); } }); // 設(shè)置子線(xiàn)程為守護(hù)線(xiàn)程 thread.setDaemon(true); System.out.println("子線(xiàn)程==守護(hù)線(xiàn)程:" + thread.isDaemon()); System.out.println("主線(xiàn)程==守護(hù)線(xiàn)程:" + Thread.currentThread().isDaemon()); }
以上程序的執(zhí)行結(jié)果為:
2.2 設(shè)置線(xiàn)程池為守護(hù)線(xiàn)程
要把線(xiàn)程池設(shè)置為守護(hù)線(xiàn)程相對(duì)來(lái)說(shuō)麻煩一些,需要將線(xiàn)程池中的所有線(xiàn)程都設(shè)置成守護(hù)線(xiàn)程,這個(gè)時(shí)候就需要使用 ThreadFactory
來(lái)定義線(xiàn)程池中每個(gè)線(xiàn)程的線(xiàn)程類(lèi)型了,具體實(shí)現(xiàn)代碼如下:
// 創(chuàng)建固定個(gè)數(shù)的線(xiàn)程池 ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); // 設(shè)置線(xiàn)程為守護(hù)線(xiàn)程 t.setDaemon(false); return t; } });
如下圖所示:
如上圖所示,可以看出,整個(gè)程序中有 10 個(gè)守護(hù)線(xiàn)程都是我創(chuàng)建的。其他幾種創(chuàng)建線(xiàn)程池的設(shè)置方式類(lèi)似,都是通過(guò) ThreadFactory
統(tǒng)一設(shè)置的,這里就不一一列舉了。
3.守護(hù)線(xiàn)程 VS 用戶(hù)線(xiàn)程
通過(guò)前面的學(xué)習(xí)我們可以創(chuàng)建兩種不同的線(xiàn)程類(lèi)型了,那二者有什么差異呢?接下來(lái)我們使用一個(gè)小示例來(lái)看一下。
下面我們創(chuàng)建一個(gè)線(xiàn)程,分別將這個(gè)線(xiàn)程設(shè)置為用戶(hù)線(xiàn)程和守護(hù)線(xiàn)程,在每個(gè)線(xiàn)程中執(zhí)行一個(gè) for
循環(huán),總共執(zhí)行 10 次信息打印,每次打印之后休眠 100 毫秒,來(lái)觀察程序的運(yùn)行結(jié)果。
3.1 用戶(hù)線(xiàn)程
新建的線(xiàn)程默認(rèn)就是用戶(hù)線(xiàn)程,因此我們無(wú)需對(duì)線(xiàn)程進(jìn)行任何特殊的處理,執(zhí)行 for
循環(huán)即可(總共執(zhí)行 10 次信息打印,每次打印之后休眠 100 毫秒),實(shí)現(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(); } } } }); // 啟動(dòng)線(xiàn)程 thread.start(); } }
以上程序執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,當(dāng)程序執(zhí)行完 10 次打印之后才會(huì)正常結(jié)束進(jìn)程。
3.2 守護(hù)線(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(); } } } }); // 設(shè)置為守護(hù)線(xiàn)程 thread.setDaemon(true); // 啟動(dòng)線(xiàn)程 thread.start(); } }
以上程序執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,當(dāng)線(xiàn)程設(shè)置為守護(hù)線(xiàn)程之后,整個(gè)程序不會(huì)等守護(hù)線(xiàn)程 for
循環(huán) 10 次之后再進(jìn)行關(guān)閉,而是當(dāng)主線(xiàn)程結(jié)束之后,守護(hù)線(xiàn)程只執(zhí)行了一次循環(huán)就結(jié)束運(yùn)行了,由此可以看出守護(hù)線(xiàn)程和用戶(hù)線(xiàn)程的不同。
3.3 小結(jié)
守護(hù)線(xiàn)程是為用戶(hù)線(xiàn)程服務(wù)的,當(dāng)一個(gè)程序中的所有用戶(hù)線(xiàn)程都執(zhí)行完成之后程序就會(huì)結(jié)束運(yùn)行,程序結(jié)束運(yùn)行時(shí)不會(huì)管守護(hù)線(xiàn)程是否正在運(yùn)行,由此我們可以看出守護(hù)線(xiàn)程在 Java 體系中權(quán)重是比較低的。
4.守護(hù)線(xiàn)程注意事項(xiàng)
守護(hù)線(xiàn)程的使用需要注意以下三個(gè)問(wèn)題:
- 守護(hù)線(xiàn)程的設(shè)置
setDaemon(true)
必須要放在線(xiàn)程的start()
之前,否則程序會(huì)報(bào)錯(cuò)。 - 在守護(hù)線(xiàn)程中創(chuàng)建的所有子線(xiàn)程都是守護(hù)線(xiàn)程。
- 使用
jojn()
方法會(huì)等待一個(gè)線(xiàn)程執(zhí)行完,無(wú)論此線(xiàn)程是用戶(hù)線(xiàn)程還是守護(hù)線(xiàn)程。
接下來(lái)我們分別演示一下,以上的注意事項(xiàng)。
4.1 setDaemon 執(zhí)行順序
當(dāng)我們將 setDaemon(true)
設(shè)置在 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(); } } } }); // 啟動(dòng)線(xiàn)程 thread.start(); // 設(shè)置為守護(hù)線(xiàn)程 thread.setDaemon(true); }
以上程序執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,當(dāng)我們將 setDaemon(true)
設(shè)置在 start()
之后,不但程序的執(zhí)行會(huì)報(bào)錯(cuò),而且設(shè)置的守護(hù)線(xiàn)程也不會(huì)生效。
4.2 守護(hù)線(xiàn)程的子線(xiàn)程
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("守護(hù)線(xiàn)程的子線(xiàn)程 thread2 isDaemon:" + thread2.isDaemon()); } }); // 設(shè)置為守護(hù)線(xiàn)程 thread.setDaemon(true); // 啟動(dòng)線(xiàn)程 thread.start(); Thread.sleep(1000); }
以上程序執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,守護(hù)線(xiàn)程中創(chuàng)建的子線(xiàn)程,默認(rèn)情況下也屬于守護(hù)線(xiàn)程。
4.3 join 與守護(hù)線(xiàn)程
通過(guò) 3.2 部分的內(nèi)容我們可以看出,默認(rèn)情況下程序結(jié)束并不會(huì)等待守護(hù)線(xiàn)程執(zhí)行完,而當(dāng)我們調(diào)用線(xiàn)程的等待方法 join()
時(shí),執(zhí)行的結(jié)果就會(huì)和 3.2 的結(jié)果有所不同,下面我們一起來(lái)看吧,
示例代碼如下:
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(); } } } }); // 設(shè)置為守護(hù)線(xiàn)程 thread.setDaemon(true); // 啟動(dòng)線(xiàn)程 thread.start(); // 等待線(xiàn)程執(zhí)行完 thread.join(); System.out.println("子線(xiàn)程==守護(hù)線(xiàn)程:" + thread.isDaemon()); System.out.println("主線(xiàn)程==守護(hù)線(xiàn)程:" + Thread.currentThread().isDaemon()); }
以上程序執(zhí)行結(jié)果如下:
通過(guò)上述結(jié)果我們可以看出,即使是守護(hù)線(xiàn)程,當(dāng)程序中調(diào)用 join()
方法時(shí),程序依然會(huì)等待守護(hù)線(xiàn)程執(zhí)行完成之后再結(jié)束進(jìn)程。
5.守護(hù)線(xiàn)程應(yīng)用場(chǎng)景
守護(hù)線(xiàn)程的典型應(yīng)用場(chǎng)景就是垃圾回收線(xiàn)程,當(dāng)然還有一些場(chǎng)景也非常適合使用守護(hù)線(xiàn)程,比如服務(wù)器端的健康檢測(cè)功能,對(duì)于一個(gè)服務(wù)器來(lái)說(shuō)健康檢測(cè)功能屬于非核心非主流的服務(wù)業(yè)務(wù),像這種為了主要業(yè)務(wù)服務(wù)的業(yè)務(wù)功能就非常合適使用守護(hù)線(xiàn)程,當(dāng)程序中的主要業(yè)務(wù)都執(zhí)行完成之后,服務(wù)業(yè)務(wù)也會(huì)跟隨者一起銷(xiāo)毀。
6.守護(hù)線(xiàn)程的執(zhí)行優(yōu)先級(jí)
首先來(lái)說(shuō),線(xiàn)程的類(lèi)型(用戶(hù)線(xiàn)程或守護(hù)線(xiàn)程)并不影響線(xiàn)程執(zhí)行的優(yōu)先級(jí),如下代碼所示,定義一個(gè)用戶(hù)線(xiàn)程和守護(hù)線(xiàn)程,分別執(zhí)行 10 萬(wàn)次循環(huán),通過(guò)觀察最后的打印結(jié)果來(lái)確認(rèn)線(xiàn)程類(lèi)型對(duì)程序執(zhí)行優(yōu)先級(jí)的影響。
public class DaemonExample { private static final int count = 100000; public static void main(String[] args) throws InterruptedException { // 定義任務(wù) Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < count; i++) { System.out.println("執(zhí)行線(xiàn)程:" + Thread.currentThread().getName()); } } }; // 創(chuàng)建守護(hù)線(xiàn)程 t1 Thread t1 = new Thread(runnable, "t1"); // 設(shè)置為守護(hù)線(xiàn)程 t1.setDaemon(true); // 啟動(dòng)線(xiàn)程 t1.start(); // 創(chuàng)建用戶(hù)線(xiàn)程 t2 Thread t2 = new Thread(runnable, "t2"); // 啟動(dòng)線(xiàn)程 t2.start(); } }
以上程序執(zhí)行結(jié)果如下:
通過(guò)上述結(jié)果可以看出,線(xiàn)程的類(lèi)型不管是守護(hù)線(xiàn)程還是用戶(hù)線(xiàn)程對(duì)程序執(zhí)行的優(yōu)先級(jí)是沒(méi)有任何影響的,而當(dāng)我們將 t2
的優(yōu)先級(jí)調(diào)整為最大時(shí),整個(gè)程序的運(yùn)行結(jié)果就完全不同了,
如下代碼所示:
public class DaemonExample { private static final int count = 100000; public static void main(String[] args) throws InterruptedException { // 定義任務(wù) Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < count; i++) { System.out.println("執(zhí)行線(xiàn)程:" + Thread.currentThread().getName()); } } }; // 創(chuàng)建守護(hù)線(xiàn)程 t1 Thread t1 = new Thread(runnable, "t1"); // 設(shè)置為守護(hù)線(xiàn)程 t1.setDaemon(true); // 啟動(dòng)線(xiàn)程 t1.start(); // 創(chuàng)建用戶(hù)線(xiàn)程 t2 Thread t2 = new Thread(runnable, "t2"); // 設(shè)置 t2 的優(yōu)先級(jí)為最高 t2.setPriority(Thread.MAX_PRIORITY); // 啟動(dòng)線(xiàn)程 t2.start(); } }
以上程序執(zhí)行結(jié)果如下:
00000000 通過(guò)上述的結(jié)果可以看出,程序的類(lèi)型和程序執(zhí)行的優(yōu)先級(jí)是沒(méi)有任何關(guān)系,當(dāng)新創(chuàng)建的線(xiàn)程默認(rèn)的優(yōu)先級(jí)都是 5 時(shí),無(wú)論是守護(hù)線(xiàn)程還是用戶(hù)線(xiàn)程,它們執(zhí)行的優(yōu)先級(jí)都是相同的,當(dāng)將二者的優(yōu)先級(jí)設(shè)置不同時(shí),執(zhí)行的結(jié)果也會(huì)隨之改變(優(yōu)先級(jí)設(shè)置的越高,最早被執(zhí)行的概率也越大)。
7.總結(jié)
在 Java 語(yǔ)言中線(xiàn)程分為用戶(hù)線(xiàn)程和守護(hù)線(xiàn)程,守護(hù)線(xiàn)程是用來(lái)為用戶(hù)線(xiàn)程服務(wù)的,當(dāng)一個(gè)程序中的所有用戶(hù)線(xiàn)程都結(jié)束之后,無(wú)論守護(hù)線(xiàn)程是否在工作都會(huì)跟隨用戶(hù)線(xiàn)程一起結(jié)束。守護(hù)線(xiàn)程從業(yè)務(wù)邏輯層面來(lái)看權(quán)重比較低,但對(duì)于線(xiàn)程調(diào)度器來(lái)說(shuō)無(wú)論是守護(hù)線(xiàn)程還是用戶(hù)線(xiàn)程,在優(yōu)先級(jí)相同的情況下被執(zhí)行的概率都是相同的。守護(hù)線(xiàn)程的經(jīng)典使用場(chǎng)景是垃圾回收線(xiàn)程,守護(hù)線(xiàn)程中創(chuàng)建的線(xiàn)程默認(rèn)情況下也都是守護(hù)線(xiàn)程。
到此這篇關(guān)于Java中用戶(hù)線(xiàn)程與守護(hù)線(xiàn)程的使用區(qū)別的文章就介紹到這了,更多相關(guān)Java線(xiàn)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
@MapperScan掃描包里混有@Service等問(wèn)題如何解決
這篇文章主要介紹了@MapperScan掃描包里混有@Service等問(wèn)題如何解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03SpringBoot使用AOP,內(nèi)部方法失效的解決方案
這篇文章主要介紹了SpringBoot使用AOP,內(nèi)部方法失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08詳解Java中日志跟蹤的簡(jiǎn)單實(shí)現(xiàn)
MDC?(Mapped?Diagnostic?Context,映射調(diào)試上下文)是?log4j??、logback及l(fā)og4j2??提供的一種方便在多線(xiàn)程條件下記錄日志的功能。本文將利用MDC實(shí)現(xiàn)簡(jiǎn)單的日志跟蹤,需要的可以參考一下2022-08-08解決SpringBoot的@DeleteMapping注解的方法不被調(diào)用問(wèn)題
這篇文章主要介紹了解決SpringBoot的@DeleteMapping注解的方法不被調(diào)用問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Java使用自動(dòng)化部署工具Gradle中的任務(wù)設(shè)定教程
Grandle使用同樣運(yùn)行于JVM上的Groovy語(yǔ)言編寫(xiě),本文會(huì)對(duì)此進(jìn)行初步夠用的講解,接下來(lái)我們就一起來(lái)看一下Java使用自動(dòng)化部署工具Gradle中的任務(wù)設(shè)定教程:2016-06-06