守護線程與用戶線程
1. 前言
本節(jié)內(nèi)容主要是對守護線程與用戶線程進行深入的講解,具體內(nèi)容點如下:
- 了解守護線程與用戶線程的定義及區(qū)別,使我們學(xué)習(xí)本節(jié)內(nèi)容的基礎(chǔ)知識點;
- 了解守護線程的特點,是我們掌握守護線程的第一步;
- 掌握守護線程的創(chuàng)建,是本節(jié)內(nèi)容的重點;
- 通過守護線程與 JVM 的退出實驗,更加深入的理解守護線程的地位以及作用,為本節(jié)內(nèi)容次重點;
- 了解守護線程的作用及使用場景,為后續(xù)開發(fā)過程中提供守護線程創(chuàng)建的知識基礎(chǔ)。
2. 守護線程與用戶線程的定義及區(qū)別
Java 中的線程分為兩類,分別為 daemon 線程(守護線程〉和 user 線程(用戶線程)。
在 JVM 啟動時會調(diào)用 main 函數(shù), main 函數(shù)所在的線程就是一個用戶線程,其實在 JVM 內(nèi)部同時還啟動了好多守護線程,比如垃圾回收線程。
守護線程定義:所謂守護線程,是指在程序運行的時候在后臺提供一種通用服務(wù)的線程。比如垃圾回收線程就是一個很稱職的守護者,并且這種線程并不屬于程序中不可或缺的部分。
因此,當(dāng)所有的非守護線程結(jié)束時,程序也就終止了,同時會殺死進程中的所有守護線程。反過來說,只要任何非守護線程還在運行,程序就不會終止。
用戶線程定義:某種意義上的主要用戶線程,只要有用戶線程未執(zhí)行完畢,JVM 虛擬機不會退出。
區(qū)別:在本質(zhì)上,用戶線程和守護線程并沒有太大區(qū)別,唯一的區(qū)別就是當(dāng)最后一個非守護線程結(jié)束時,JVM 會正常退出,而不管當(dāng)前是否有守護線程,也就是說守護線程是否結(jié)束并不影響 JVM 的退出。
言外之意,只要有一個用戶線程還沒結(jié)束, 正常情況下 JVM 就不會退出。
3. 守護線程的特點
Java 中的守護線程和 Linux 中的守護進程是有些區(qū)別的,Linux 守護進程是系統(tǒng)級別的,當(dāng)系統(tǒng)退出時,才會終止。
而 Java 中的守護線程是 JVM 級別的,當(dāng) JVM 中無任何用戶進程時,守護進程銷毀,JVM 退出,程序終止??偨Y(jié)來說,Java 守護進程的最主要的特點有:
- 守護線程是運行在程序后臺的線程;
- 守護線程創(chuàng)建的線程,依然是守護線程;
- 守護線程不會影響 JVM 的退出,當(dāng) JVM 只剩余守護線程時,JVM 進行退出;
- 守護線程在 JVM 退出時,自動銷毀。
4. 守護線程的創(chuàng)建
創(chuàng)建方式:將線程轉(zhuǎn)換為守護線程可以通過調(diào)用 Thread 對象的 setDaemon (true) 方法來實現(xiàn)。
創(chuàng)建細節(jié):
- thread.setDaemon (true) 必須在 thread.start () 之前設(shè)置,否則會跑出一個 llegalThreadStateException 異常。你不能把正在運行的常規(guī)線程設(shè)置為守護線程;
- 在 Daemon 線程中產(chǎn)生的新線程也是 Daemon 的;
- 守護線程應(yīng)該永遠不去訪問固有資源,如文件、數(shù)據(jù)庫,因為它會在任何時候甚至在一個操作的中間發(fā)生中斷。
線程創(chuàng)建代碼示例:
public class DemoTest {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
//代碼執(zhí)行邏輯
}
});
threadOne.setDaemon(true); //設(shè)置threadOne為守護線程
threadOne. start();
}
}
5. 守護線程與 JVM 的退出實驗
為了更好的了解守護線程與 JVM 是否退出的關(guān)系,我們首先來設(shè)計一個守護線程正在運行,但用戶線程執(zhí)行完畢導(dǎo)致的 JVM 退出的場景。
場景設(shè)計:
- 創(chuàng)建 1 個線程,線程名為 threadOne;
- run 方法線程 sleep 1000 毫秒后,進行求和計算,求解 1 + 2 + 3 + … + 100 的值;
- 將線程 threadOne 設(shè)置為守護線程;
- 執(zhí)行代碼,最終打印的結(jié)果;
- 加入 join 方法,強制讓用戶線程等待守護線程 threadOne;
- 執(zhí)行代碼,最終打印的結(jié)果。
期望結(jié)果:
- 未加入 join 方法之前,threadOne 不能執(zhí)行求和邏輯,無打印輸出,因為 main 函數(shù)線程執(zhí)行完畢后,JVM 退出,守護線程也就隨之死亡,無打印結(jié)果;
- 加入 join 方法后,可以打印求和結(jié)果,因為 main 函數(shù)線程需要等待 threadOne 線程執(zhí)行完畢后才繼續(xù)向下執(zhí)行,main 函數(shù)執(zhí)行完畢,JVM 退出。
Tips:main 函數(shù)就是一個用戶線程,main 方法執(zhí)行時,只有一個用戶線程,如果 main 函數(shù)執(zhí)行完畢,用戶線程銷毀,JVM 退出,此時不會考慮守護線程是否執(zhí)行完畢,直接退出。
代碼實現(xiàn) - 不加入 join 方法:
public class DemoTest {
public static void main(String[] args){
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
System.out.println("守護線程,最終求和的值為: " + sum);
}
});
threadOne.setDaemon(true); //設(shè)置threadOne為守護線程
threadOne. start();
System.out.println("main 函數(shù)線程執(zhí)行完畢, JVM 退出。");
}
}
執(zhí)行結(jié)果驗證:
main 函數(shù)線程執(zhí)行完畢, JVM 退出。
從結(jié)果上可以看到,JVM 退出了,守護線程還沒來得及執(zhí)行,也就隨著 JVM 的退出而消亡了。
代碼實現(xiàn) - 加入 join 方法:
public class DemoTest {
public static void main(String[] args){
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
System.out.println("守護線程,最終求和的值為: " + sum);
}
});
threadOne.setDaemon(true); //設(shè)置threadOne為守護線程
threadOne. start();
try {
threadOne.join(); // 加入join 方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main 函數(shù)線程執(zhí)行完畢, JVM 退出。");
}
}
執(zhí)行結(jié)果驗證:
守護線程,最終求和的值為: 5050
main 函數(shù)線程執(zhí)行完畢, JVM 退出。
從結(jié)果來看,守護線程不決定 JVM 的退出,除非強制使用 join 方法使用戶線程等待守護線程的執(zhí)行結(jié)果,但是實際的開發(fā)過程中,這樣的操作是不允許的,因為守護線程,默認就是不需要被用戶線程等待的,是服務(wù)于用戶線程的。
6. 守護線程的作用及使用場景
作用:我們以 GC 垃圾回收線程舉例,它就是一個經(jīng)典的守護線程,當(dāng)我們的程序中不再有任何運行的 Thread, 程序就不會再產(chǎn)生垃圾,垃圾回收器也就無事可做,所以當(dāng)垃圾回收線程是 JVM 上僅剩的線程時,垃圾回收線程會自動離開。
它始終在低級別的狀態(tài)中運行,用于實時監(jiān)控和管理系統(tǒng)中的可回收資源。
應(yīng)用場景:
- 為其它線程提供服務(wù)支持的情況,可選用守護線程;
- 根據(jù)開發(fā)需求,程序結(jié)束時,這個線程必須正常且立刻關(guān)閉,就可以作為守護線程來使用;
- 如果一個正在執(zhí)行某個操作的線程必須要執(zhí)行完畢后再釋放,否則就會出現(xiàn)不良的后果的話,那么這個線程就不能是守護線程,而是用戶線程;
- 正常開發(fā)過程中,一般心跳監(jiān)聽,垃圾回收,臨時數(shù)據(jù)清理等通用服務(wù)會選擇守護線程。
7. 小結(jié)
掌握用戶線程和守護線程的區(qū)別點非常重要,在實際的工作開發(fā)中,對一些服務(wù)型,通用型的線程服務(wù)可以根據(jù)需要選擇守護線程進行執(zhí)行,這樣可以減少 JVM 不可退出的現(xiàn)象,并且可以更好地協(xié)調(diào)不同種類的線程之間的協(xié)作,減少守護線程對高優(yōu)先級的用戶線程的資源爭奪,使系統(tǒng)更加的穩(wěn)定。
本節(jié)的重中之重是掌握守護線程的創(chuàng)建以及創(chuàng)建需要注意的事項,了解守護線程與用戶線程的區(qū)別使我們掌握守護線程的前提。