首頁 > 軟體

Java中使用者執行緒與守護執行緒的使用區別

2022-05-19 13:00:32

前言;

在 Java 語言中執行緒分為兩類:使用者執行緒和守護執行緒,而二者之間的區別卻鮮有人知,所以本文磊哥帶你來看二者之間的區別,以及守護執行緒需要注意的一些事項。

1.預設使用者執行緒

Java 語言中無論是執行緒還是執行緒池,預設都是使用者執行緒,因此使用者執行緒也被成為普通執行緒。

以執行緒為例,想要檢視執行緒是否為守護執行緒只需通過呼叫 isDaemon() 方法查詢即可,如果查詢的值為 false 則表示不為守護執行緒,自然也就屬於使用者執行緒了,

如下程式碼所示:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是子執行緒");
        }
    });
    System.out.println("子執行緒==守護執行緒:" + thread.isDaemon());
    System.out.println("主執行緒==守護執行緒:" + Thread.currentThread().isDaemon());
}

以上程式的執行結果為: 

 從上述結果可以看出,預設情況下主執行緒和建立的新執行緒都為使用者執行緒

PS:Thread.currentThread() 的意思是獲取執行當前程式碼的執行緒範例。

2.主動修改為守護執行緒

守護執行緒(Daemon Thread)也被稱之為後臺執行緒或服務執行緒,守護執行緒是為使用者執行緒服務的,當程式中的使用者執行緒全部執行結束之後,守護執行緒也會跟隨結束。

守護執行緒的角色就像“服務員”,而使用者執行緒的角色就像“顧客”,當“顧客”全部走了之後(全部執行結束),那“服務員”(守護執行緒)也就沒有了存在的意義,所以當一個程式中的全部使用者執行緒都結束執行之後,那麼無論守護執行緒是否還在工作都會隨著使用者執行緒一塊結束,整個程式也會隨之結束執行。

那如何將預設的使用者執行緒修改為守護執行緒呢?

這個問題要分為兩種情況來回答,首先如果是執行緒,則可以通過設定 setDaemon(true) 方法將使用者執行緒直接修改為守護執行緒,而如果是執行緒池則需要通過 ThreadFactory 將執行緒池中的每個執行緒都為守護執行緒才行,接下來我們分別來實現一下。

2.1 設定執行緒為守護執行緒

如果使用的是執行緒,可以通過 setDaemon(true) 方法將執行緒型別更改為守護執行緒,如下程式碼所示:

 public static void main(String[] args) throws InterruptedException {
     Thread thread = new Thread(new Runnable() {
         @Override
         public void run() {
             System.out.println("我是子執行緒");
         }
     });
     // 設定子執行緒為守護執行緒
     thread.setDaemon(true);
     System.out.println("子執行緒==守護執行緒:" + thread.isDaemon());
     System.out.println("主執行緒==守護執行緒:" + Thread.currentThread().isDaemon());
 }

以上程式的執行結果為: 

2.2 設定執行緒池為守護執行緒

要把執行緒池設定為守護執行緒相對來說麻煩一些,需要將執行緒池中的所有執行緒都設定成守護執行緒,這個時候就需要使用 ThreadFactory 來定義執行緒池中每個執行緒的執行緒型別了,具體實現程式碼如下:

// 建立固定個數的執行緒池
ExecutorService threadPool = Executors.newFixedThreadPool(10, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        // 設定執行緒為守護執行緒
        t.setDaemon(false);
        return t;
    }
});

如下圖所示: 

如上圖所示,可以看出,整個程式中有 10 個守護執行緒都是我建立的。其他幾種建立執行緒池的設定方式類似,都是通過 ThreadFactory 統一設定的,這裡就不一一列舉了。

3.守護執行緒 VS 使用者執行緒

通過前面的學習我們可以建立兩種不同的執行緒型別了,那二者有什麼差異呢?接下來我們使用一個小范例來看一下。

下面我們建立一個執行緒,分別將這個執行緒設定為使用者執行緒和守護執行緒,在每個執行緒中執行一個 for 迴圈,總共執行 10 次資訊列印,每次列印之後休眠 100 毫秒,來觀察程式的執行結果。

3.1 使用者執行緒

新建的執行緒預設就是使用者執行緒,因此我們無需對執行緒進行任何特殊的處理,執行 for 迴圈即可(總共執行 10 次資訊列印,每次列印之後休眠 100 毫秒),實現程式碼如下:

/**
 * 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();
                    }
                }
            }
        });
        // 啟動執行緒
        thread.start();
    }
}

以上程式執行結果如下: 

 從上述結果可以看出,當程式執行完 10 次列印之後才會正常結束程序。

3.2 守護執行緒

/**
 * 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();
                    }
                }
            }
        });
        // 設定為守護執行緒
        thread.setDaemon(true);
        // 啟動執行緒
        thread.start();
    }
}

以上程式執行結果如下: 

 從上述結果可以看出,當執行緒設定為守護執行緒之後,整個程式不會等守護執行緒 for 迴圈 10 次之後再進行關閉,而是當主執行緒結束之後,守護執行緒只執行了一次迴圈就結束執行了,由此可以看出守護執行緒和使用者執行緒的不同。

3.3 小結

守護執行緒是為使用者執行緒服務的,當一個程式中的所有使用者執行緒都執行完成之後程式就會結束執行,程式結束執行時不會管守護執行緒是否正在執行,由此我們可以看出守護執行緒在 Java 體系中權重是比較低的。

4.守護執行緒注意事項

守護執行緒的使用需要注意以下三個問題:

  • 守護執行緒的設定 setDaemon(true) 必須要放線上程的 start() 之前,否則程式會報錯。
  • 在守護執行緒中建立的所有子執行緒都是守護執行緒。
  • 使用 jojn() 方法會等待一個執行緒執行完,無論此執行緒是使用者執行緒還是守護執行緒。

接下來我們分別演示一下,以上的注意事項。

4.1 setDaemon 執行順序

當我們將 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);
}

以上程式執行結果如下: 

 從上述結果可以看出,當我們將 setDaemon(true) 設定在 start() 之後,不但程式的執行會報錯,而且設定的守護執行緒也不會生效。

4.2 守護執行緒的子執行緒

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("守護執行緒的子執行緒 thread2 isDaemon:" +
                              thread2.isDaemon());
        }
    });
    // 設定為守護執行緒
    thread.setDaemon(true);
    // 啟動執行緒
    thread.start();

    Thread.sleep(1000);
}

以上程式執行結果如下: 

 從上述結果可以看出,守護執行緒中建立的子執行緒,預設情況下也屬於守護執行緒

4.3 join 與守護執行緒

通過 3.2 部分的內容我們可以看出,預設情況下程式結束並不會等待守護執行緒執行完,而當我們呼叫執行緒的等待方法 join() 時,執行的結果就會和 3.2 的結果有所不同,下面我們一起來看吧,

範例程式碼如下:

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();
    // 等待執行緒執行完
    thread.join();
    System.out.println("子執行緒==守護執行緒:" + thread.isDaemon());
    System.out.println("主執行緒==守護執行緒:" + Thread.currentThread().isDaemon());
}

以上程式執行結果如下: 

 通過上述結果我們可以看出,即使是守護執行緒,當程式中呼叫 join() 方法時,程式依然會等待守護執行緒執行完成之後再結束程序。

5.守護執行緒應用場景

守護執行緒的典型應用場景就是垃圾回收執行緒,當然還有一些場景也非常適合使用守護執行緒,比如伺服器端的健康檢測功能,對於一個伺服器來說健康檢測功能屬於非核心非主流的服務業務,像這種為了主要業務服務的業務功能就非常合適使用守護執行緒,當程式中的主要業務都執行完成之後,服務業務也會跟隨者一起銷燬。

6.守護執行緒的執行優先順序

首先來說,執行緒的型別(使用者執行緒或守護執行緒)並不影響執行緒執行的優先順序,如下程式碼所示,定義一個使用者執行緒和守護執行緒,分別執行 10 萬次迴圈,通過觀察最後的列印結果來確認執行緒型別對程式執行優先順序的影響。

public class DaemonExample {
    private static final int count = 100000;
    public static void main(String[] args) throws InterruptedException {
        // 定義任務
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < count; i++) {
                    System.out.println("執行執行緒:" + Thread.currentThread().getName());
                }
            }
        };
        // 建立守護執行緒 t1
        Thread t1 = new Thread(runnable, "t1");
        // 設定為守護執行緒
        t1.setDaemon(true);
        // 啟動執行緒
        t1.start();
        // 建立使用者執行緒 t2
        Thread t2 = new Thread(runnable, "t2");
        // 啟動執行緒
        t2.start();
    }
}

以上程式執行結果如下: 

 通過上述結果可以看出,執行緒的型別不管是守護執行緒還是使用者執行緒對程式執行的優先順序是沒有任何影響的,而當我們將 t2 的優先順序調整為最大時,整個程式的執行結果就完全不同了,

如下程式碼所示:

public class DaemonExample {
    private static final int count = 100000;
    public static void main(String[] args) throws InterruptedException {
        // 定義任務
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < count; i++) {
                    System.out.println("執行執行緒:" + Thread.currentThread().getName());
                }
            }
        };
        // 建立守護執行緒 t1
        Thread t1 = new Thread(runnable, "t1");
        // 設定為守護執行緒
        t1.setDaemon(true);
        // 啟動執行緒
        t1.start();
        // 建立使用者執行緒 t2
        Thread t2 = new Thread(runnable, "t2");
        // 設定 t2 的優先順序為最高
        t2.setPriority(Thread.MAX_PRIORITY);
        // 啟動執行緒
        t2.start();
    }
}

以上程式執行結果如下: 

00000000 通過上述的結果可以看出,程式的型別和程式執行的優先順序是沒有任何關係,當新建立的執行緒預設的優先順序都是 5 時,無論是守護執行緒還是使用者執行緒,它們執行的優先順序都是相同的,當將二者的優先順序設定不同時,執行的結果也會隨之改變(優先順序設定的越高,最早被執行的概率也越大)。

7.總結

在 Java 語言中執行緒分為使用者執行緒和守護執行緒,守護執行緒是用來為使用者執行緒服務的,當一個程式中的所有使用者執行緒都結束之後,無論守護執行緒是否在工作都會跟隨使用者執行緒一起結束。守護執行緒從業務邏輯層面來看權重比較低,但對於執行緒排程器來說無論是守護執行緒還是使用者執行緒,在優先順序相同的情況下被執行的概率都是相同的。守護執行緒的經典使用場景是垃圾回收執行緒,守護執行緒中建立的執行緒預設情況下也都是守護執行緒。

到此這篇關於Java中使用者執行緒與守護執行緒的使用區別的文章就介紹到這了,更多相關Java執行緒內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com