面試準備不充分,被Java守護執行緒幹懵了,面試官主打一個東西沒用但你得會

JavaBuild發表於2024-03-09

寫在開頭

面試官:小夥子請聊一聊Java中的精靈執行緒?
我:什麼?精靈執行緒?啥時候精靈執行緒?
面試官:精靈執行緒沒聽過?那守護執行緒呢?
我:守護執行緒知道,就是為普通執行緒服務的執行緒嘛。
面試官:沒了?守護執行緒的特點,怎麼使用,需要注意啥,Java中經典的守護執行緒都有啥?
我:不知道。。。
這的天,面試一個10K的工作,上來先整個精靈執行緒,直接把人整蒙了,難道提及Java多執行緒的時候,問的不應該是執行緒、執行緒池、併發衝突解決方案、如何加鎖,以及各種鎖的知識點嗎?上來整個偏門的守護執行緒,這是出心的不想要啊。

何為守護執行緒

上面這段內容是在牛客上看到的,說實話這位面試官問的這內容確實主要一個:東西沒用,但你得知道!可如果說他問的真是離譜嗎?也算不上,精靈執行緒我們很少聽到,但守護執行緒我們在學習Java執行緒的時候肯定有所耳聞!那麼今天我們就一起來小酌一下這個 守護執行緒

Java中的執行緒分為2種:使用者執行緒守護執行緒

使用者執行緒又叫普通執行緒,是我們驅動業務邏輯運轉的核心;而守護執行緒,顧名思義,是守護使用者執行緒的一種執行緒,執行在後臺提供通用服務,因此也叫後臺執行緒或者精靈執行緒。

守護執行緒的使用場景

那在Java中這個守護執行緒都有什麼實際用處,或者說應用場景呢?

  1. GC垃圾回收執行緒:這是JVM中非常經典的一個守護執行緒,它始終以低階別狀態執行,用於實時監控和管理系統中的可回收資源,一旦我們的系統沒有任何執行的使用者執行緒時,程式也就不會再產生垃圾,這時,無事可做的垃圾回收執行緒會自動結束。
  2. 應用指標統計:部分服務可以透過守護執行緒來採取應用指標,服務結束則停止採集。

怎麼設定守護執行緒

那我們在程式碼中,如何將一個執行緒設定為守護執行緒呢?咱們可以透過在 start 執行緒之前呼叫執行緒的 setDaemon(true) 方法,將一個執行緒設定為守護執行緒,來看一下下面的這個demo。

【程式碼例項1】

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread("守護執行緒"){
            @Override
            public void run() {
                int i = 0;
                while (i <= 4){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                super.run();
            }
        };
        Thread thread2 = new Thread("使用者執行緒"){
            @Override
            public void run() {
                int i = 0;
                while (i < 2){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                super.run();
            }
        };
        //setDaemon, 不設定則預設false
        thread1.setDaemon(true);//設定thread1為守護執行緒
        thread2.setDaemon(false);//設定thread2為普通執行緒
        thread1.start();
        thread2.start();
    }
}

輸出:

守護執行緒:1
使用者執行緒:1
使用者執行緒:2
守護執行緒:2

這段測試程式碼中,我們透過thread1.setDaemon(true)將執行緒1設定成了一個守護執行緒(false為普通執行緒),使用者執行緒的迴圈次數為2,使用者執行緒的迴圈次數為4,但當程式中的使用者執行緒執行完之後,守護執行緒並沒有繼續向下迴圈,而是隨著使用者執行緒的結束而自我終止了。

守護執行緒的優先順序

看到網上很多博文提到了守護執行緒的優先順序問題,都說守護執行緒的優先順序比較低,那我們透過一段測試用例看一下真實情況。

【程式碼例項2】

public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread("守護執行緒"){
            @Override
            public void run() {
                int i = 0;
                while (i <= 4){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i+"-優先順序:" +Thread.currentThread().getPriority());
                }
                super.run();
            }
        };
        Thread thread2 = new Thread("使用者執行緒"){
            @Override
            public void run() {
                int i = 0;
                while (i < 2){
                    i++;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i+"-優先順序:" +Thread.currentThread().getPriority());
                }
                super.run();
            }
        };

        //setDaemon, 不設定則預設false
        thread1.setDaemon(true);//設定thread1為守護執行緒
        thread2.setDaemon(false);//設定thread2為普通執行緒

        thread1.start();
        thread2.start();

        for (int i = 0; i <5 ; i++) {
            System.out.println("主執行緒:"+i+"-優先順序:" +Thread.currentThread().getPriority());
        }
    }
}

輸出:

主執行緒:0-優先順序:5
主執行緒:1-優先順序:5
主執行緒:2-優先順序:5
主執行緒:3-優先順序:5
主執行緒:4-優先順序:5
使用者執行緒:1-優先順序:5
守護執行緒:1-優先順序:5
使用者執行緒:2-優先順序:5
守護執行緒:2-優先順序:5

這個測試結果是不是出乎意料?無論是主執行緒還是普通的使用者執行緒,又或者說守護執行緒,他們的優先順序都是5,優先順序竟然都一樣!

我們知道所謂的執行緒就是CPU 排程和分派的基本單位,根據優先順序不同,來決定獲取CPU時間片的先後順序,因為主執行緒啟動時,其他執行緒還沒有啟動,所以這時候它最先獲得CPU排程許可權;

又因為其他執行緒存在休眠時間,這個時間段上足夠主執行緒執行完畢。主執行緒執行完後,使用者執行緒和守護執行緒互相搶佔CPU資源,交錯執行,直至程式中沒有普通執行緒為止!若沒有休眠時間,且迴圈次數足夠多時,我們可以看到主執行緒、守護執行緒、使用者執行緒都競爭CPU時間片,呈現交錯執行的結果!

注意事項

在設定執行緒為守護執行緒的時候要注意一個事情,那就是當 start(); 放到 setDaemon(true); 之前,程式丟擲IllegalThreadStateException。如下圖:
image

原因是 setDaemon(true)原始碼中,有一個isAlive()的判斷,判斷當前執行緒的狀態是否為活躍執行緒,若是則丟擲異常,我們不能修改一個正在執行中的執行緒!

【原始碼解析1】

  public final void setDaemon(boolean on) {
        checkAccess();
        //執行緒已經啟動後,不可修改,否則丟擲非法執行緒狀態異常
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

總結

OK,寫到這裡,關於守護執行緒的內容就講完了,我們從什麼是守護執行緒,守護執行緒的使用場景,優先順序,注意事項等方面,進行了全面的介紹。

其實說實話,在我們日後工作中,很少直接使用上守護執行緒,所以它看似沒那麼重要,但在很多Java多執行緒相關的書籍中絕對都有提及,很多小夥伴在學習的過程中認為這個點不重要,也就相當然的忽略了,但遇到變態的面試官,專門挑揀一些偏僻的知識點考你時,難免陷入尷尬,所以希望藉助這個考題,大家能夠在日後更細心的學習哈。

結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

image

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

image

相關文章