亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

守護線程與用戶線程

1. 前言

本節內容主要是對守護線程與用戶線程進行深入的講解,具體內容點如下:

  • 了解守護線程與用戶線程的定義及區別,使我們學習本節內容的基礎知識點;
  • 了解守護線程的特點,是我們掌握守護線程的第一步;
  • 掌握守護線程的創建,是本節內容的重點;
  • 通過守護線程與 JVM 的退出實驗,更加深入的理解守護線程的地位以及作用,為本節內容次重點;
  • 了解守護線程的作用及使用場景,為后續開發過程中提供守護線程創建的知識基礎。

2. 守護線程與用戶線程的定義及區別

Java 中的線程分為兩類,分別為 daemon 線程(守護線程〉和 user 線程(用戶線程)。

在 JVM 啟動時會調用 main 函數, main 函數所在的線程就是一個用戶線程,其實在 JVM 內部同時還啟動了好多守護線程,比如垃圾回收線程。

守護線程定義:所謂守護線程,是指在程序運行的時候在后臺提供一種通用服務的線程。比如垃圾回收線程就是一個很稱職的守護者,并且這種線程并不屬于程序中不可或缺的部分。

因此,當所有的非守護線程結束時,程序也就終止了,同時會殺死進程中的所有守護線程。反過來說,只要任何非守護線程還在運行,程序就不會終止。

用戶線程定義:某種意義上的主要用戶線程,只要有用戶線程未執行完畢,JVM 虛擬機不會退出。

區別:在本質上,用戶線程和守護線程并沒有太大區別,唯一的區別就是當最后一個非守護線程結束時,JVM 會正常退出,而不管當前是否有守護線程,也就是說守護線程是否結束并不影響 JVM 的退出。

言外之意,只要有一個用戶線程還沒結束, 正常情況下 JVM 就不會退出。

3. 守護線程的特點

Java 中的守護線程和 Linux 中的守護進程是有些區別的,Linux 守護進程是系統級別的,當系統退出時,才會終止。

而 Java 中的守護線程是 JVM 級別的,當 JVM 中無任何用戶進程時,守護進程銷毀,JVM 退出,程序終止。總結來說,Java 守護進程的最主要的特點有:

  • 守護線程是運行在程序后臺的線程;
  • 守護線程創建的線程,依然是守護線程;
  • 守護線程不會影響 JVM 的退出,當 JVM 只剩余守護線程時,JVM 進行退出;
  • 守護線程在 JVM 退出時,自動銷毀。

4. 守護線程的創建

創建方式:將線程轉換為守護線程可以通過調用 Thread 對象的 setDaemon (true) 方法來實現。

創建細節

  • thread.setDaemon (true) 必須在 thread.start () 之前設置,否則會跑出一個 llegalThreadStateException 異常。你不能把正在運行的常規線程設置為守護線程;
  • 在 Daemon 線程中產生的新線程也是 Daemon 的;
  • 守護線程應該永遠不去訪問固有資源,如文件、數據庫,因為它會在任何時候甚至在一個操作的中間發生中斷。

線程創建代碼示例

public class DemoTest {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                //代碼執行邏輯
            }
        });
        threadOne.setDaemon(true); //設置threadOne為守護線程
        threadOne. start();
    }
}

5. 守護線程與 JVM 的退出實驗

為了更好的了解守護線程與 JVM 是否退出的關系,我們首先來設計一個守護線程正在運行,但用戶線程執行完畢導致的 JVM 退出的場景。

場景設計

  • 創建 1 個線程,線程名為 threadOne;
  • run 方法線程 sleep 1000 毫秒后,進行求和計算,求解 1 + 2 + 3 + … + 100 的值;
  • 將線程 threadOne 設置為守護線程;
  • 執行代碼,最終打印的結果;
  • 加入 join 方法,強制讓用戶線程等待守護線程 threadOne;
  • 執行代碼,最終打印的結果。

期望結果

  • 未加入 join 方法之前,threadOne 不能執行求和邏輯,無打印輸出,因為 main 函數線程執行完畢后,JVM 退出,守護線程也就隨之死亡,無打印結果;
  • 加入 join 方法后,可以打印求和結果,因為 main 函數線程需要等待 threadOne 線程執行完畢后才繼續向下執行,main 函數執行完畢,JVM 退出。

Tips:main 函數就是一個用戶線程,main 方法執行時,只有一個用戶線程,如果 main 函數執行完畢,用戶線程銷毀,JVM 退出,此時不會考慮守護線程是否執行完畢,直接退出。

代碼實現 - 不加入 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); //設置threadOne為守護線程
        threadOne. start();
        System.out.println("main 函數線程執行完畢, JVM 退出。");
    }
}

執行結果驗證

main 函數線程執行完畢, JVM 退出。

從結果上可以看到,JVM 退出了,守護線程還沒來得及執行,也就隨著 JVM 的退出而消亡了。

代碼實現 - 加入 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); //設置threadOne為守護線程
        threadOne. start();
        try {
            threadOne.join(); // 加入join 方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main 函數線程執行完畢, JVM 退出。");
    }
}

執行結果驗證

守護線程,最終求和的值為: 5050
main 函數線程執行完畢, JVM 退出。

從結果來看,守護線程不決定 JVM 的退出,除非強制使用 join 方法使用戶線程等待守護線程的執行結果,但是實際的開發過程中,這樣的操作是不允許的,因為守護線程,默認就是不需要被用戶線程等待的,是服務于用戶線程的。

6. 守護線程的作用及使用場景

作用:我們以 GC 垃圾回收線程舉例,它就是一個經典的守護線程,當我們的程序中不再有任何運行的 Thread, 程序就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收線程是 JVM 上僅剩的線程時,垃圾回收線程會自動離開。

它始終在低級別的狀態中運行,用于實時監控和管理系統中的可回收資源。

應用場景

  • 為其它線程提供服務支持的情況,可選用守護線程;
  • 根據開發需求,程序結束時,這個線程必須正常且立刻關閉,就可以作為守護線程來使用;
  • 如果一個正在執行某個操作的線程必須要執行完畢后再釋放,否則就會出現不良的后果的話,那么這個線程就不能是守護線程,而是用戶線程;
  • 正常開發過程中,一般心跳監聽,垃圾回收,臨時數據清理等通用服務會選擇守護線程。

7. 小結

掌握用戶線程和守護線程的區別點非常重要,在實際的工作開發中,對一些服務型,通用型的線程服務可以根據需要選擇守護線程進行執行,這樣可以減少 JVM 不可退出的現象,并且可以更好地協調不同種類的線程之間的協作,減少守護線程對高優先級的用戶線程的資源爭奪,使系統更加的穩定。

本節的重中之重是掌握守護線程的創建以及創建需要注意的事項,了解守護線程與用戶線程的區別使我們掌握守護線程的前提。