面試突擊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 ? "守護線程" : "用戶線程"));
    }
}

以上程序的執行結果如下圖所示:
image.png
從上述結果可以看出,默認情況下創建的線程和線程池都是用戶線程。

守護線程定義

守護線程(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 ? "守護線程" : "用戶線程"));
}

以上程序的執行結果如下圖所示:
image.png

將線程池設置為守護線程

要把線程池設置為守護線程相對來說麻煩一些,需要將線程池中的所有線程都設置成守護線程,這個時候就需要使用線程工廠 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);
}

以上程序的執行結果如下圖所示:
image.png

守護線程 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();
}

以上程序的執行結果如下圖所示:
image.png
從上述結果可以看出,當程序執行完 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();
}

以上程序執行結果如下圖所示:
image.png
從上述結果可以看出,當線程設置為守護線程之后,整個程序不會等守護線程 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);
}

以上程序執行結果如下圖所示:
image.png
從上述結果可以看出,當我們將 setDaemon(true) 設置在 start() 之后,不但程序的執行會報錯,而且設置的守護線程也不會生效。

總結

在 Java 語言中線程分為兩類:用戶線程和守護線程,默認情況下我們創建的線程或線程池都是用戶線程,守護線程是為用戶線程服務的,當一個程序中的所有用戶線程都執行完成之后程序就會結束運行,程序結束運行時不會管守護線程是否正在運行,由此我們可以看出守護線程在 Java 體系中權重是比較低的,這就是守護線程和用戶線程的區別。

是非審之於己,毀譽聽之於人,得失安之於數。

公眾號:Java面試真題解析

面試合集:https://gitee.com/mydb/interview


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM