­

java中的守護執行緒

  • 2019 年 10 月 20 日
  • 筆記

在Java中有兩類執行緒,分別是User Thread(用戶執行緒)和Daemon Thread(守護執行緒) 。

用戶執行緒很好理解,我們日常開發中編寫的業務邏輯程式碼,運行起來都是一個個用戶執行緒。而守護執行緒相對來說則要特別理解一下。

什麼是守護執行緒

在作業系統裡面是沒有所謂的守護執行緒的概念的,只有守護進程一說。但是Java語言機制是構建在JVM的基礎之上的,這一機制意味著Java平台是把作業系統的底層給屏蔽了起來,所以它可以在它自己的虛擬的平台裡面構造出對自己有利的機制。而Java語言或者說平台的設計者多多少少是收到Unix作業系統思想的影響,而守護執行緒機制又是對JVM這樣的平台湊合,於是守護執行緒應運而生。

所謂的守護執行緒,指的是程式運行時在後台提供的一種通用服務的執行緒。比如垃圾回收執行緒就是一個很稱職的守護者,並且這種執行緒並不屬於程式中不可或缺的部分。因此,當所有的非守護執行緒結束時,程式也就終止了,同時會殺死進程中的所有守護執行緒。反過來說,只要任何非守護執行緒還在運行,程式就不會終止。

事實上,User Thread(用戶執行緒)和Daemon Thread(守護執行緒)從本質上來說並沒有什麼區別,唯一的不同之處就在於虛擬機的離開:如果用戶執行緒已經全部退出運行了,只剩下守護執行緒存在了,虛擬機也就退出了。 因為沒有了被守護者,守護執行緒也就沒有工作可做了,也就沒有繼續運行程式的必要了。

守護執行緒的使用與注意事項

守護執行緒並非只有虛擬機內部可以提供,用戶也可以手動將一個用戶執行緒設定/轉換為守護執行緒。

在Thread類中提供了一個setDaemon(true)方法來將一個普通的執行緒(用戶執行緒)設置為守護執行緒。

public final void setDaemon(boolean on);

在使用的過程中,有幾點需要注意:

1.thread.setDaemon(true)必須在thread.start()之前設置,否則會拋出一個IllegalThreadStateException異常。這也就意味著不能把正在運行的常規執行緒設置為守護執行緒。 這點與作業系統中的守護進程有著明顯的區別,守護進程是創建後,讓進程擺脫原會話的控制+讓進程擺脫原進程組的控制+讓進程擺脫原控制終端的控制;所以說寄託於虛擬機的語言機制跟系統級語言有著本質上面的區別。

2.在Daemon執行緒中產生的新執行緒也是Daemon的。關於這一點又是與作業系統中的守護進程有著本質的區別:守護進程fork()出來的子進程不再是守護進程,儘管它把父進程的進程相關資訊複製過去了,但是子進程的進程的父進程不是init進程,所謂的守護進程本質上說就是,當父進程掛掉,init就會收養該進程,然後文件0、1和2都是/dev/null,當前目錄到/。

3.不是所有的應用都可以分配給Daemon執行緒來進行服務的,比如讀寫操作或者計算邏輯。因為這種應用可能在Daemon Thread還沒來得及進行操作時,虛擬機已經退出了。這也就意味著,守護執行緒應該永遠不去訪問固有資源,如文件、資料庫,因為它會在任何時候甚至在一個操作的中間發生中斷。

下面以一個完成文件輸出的守護執行緒任務作為例子:

import java.io.*;    class TestRunnable implements Runnable {      public void run(){          try {              Thread.sleep(1000); // 守護執行緒阻塞1秒後運行                File f = new File("daemon.txt");              FileOutputStream os = new FileOutputStream(f,true);              os.write("daemon".getBytes());          } catch(IOException e1) {              e1.printStackTrace();          } catch(InterruptedException e2) {              e2.printStackTrace();          }      }  }    public class TestDemo2 {      public static void main(String[] args) throws InterruptedException {          Runnable tr = new TestRunnable();          Thread thread = new Thread(tr);          thread.setDaemon(true); // 設置守護執行緒(必須在thread.start()之前)          thread.start(); // 開始執行分進程      }  }

上面這段程式碼的運行結果是文件daemon.txt中沒有daemon字元串。

但是如果把thread.setDaemon(true);這行程式碼注釋掉,文件daemon.txt是可以被寫入daemon字元串的,因為這個時候這個執行緒就是普通的用戶執行緒了。

簡單理解就是,JRE判斷程式是否執行結束的標準是所有的前台執行緒(用戶執行緒)執行完畢了,而不管後台執行緒(守護執行緒)的狀態。

守護執行緒的應用場景

前面說了那麼多,那麼Daemon Thread的實際應用在那裡呢?舉個例子,Web伺服器中的Servlet,在容器啟動時,後台都會初始化一個服務執行緒,即調度執行緒,負責處理http請求,然後每個請求過來,調度執行緒就會從執行緒池中取出一個工作者執行緒來處理該請求,從而實現並發控制的目的。也就是說,一個實際應用在Java的執行緒池中的調度執行緒。

總結

從我的理解,守護執行緒就是用來告訴JVM,我的這個執行緒是一個低級別的執行緒,不需要等待它運行完才退出,讓JVM喜歡什麼時候退出就退出,不用管這個執行緒。

在日常的業務相關的CRUD開發中,其實並不會關注到守護執行緒這個概念,也幾乎不會用上。

但是如果要往更高的地方走的話,這些深層次的概念還是要了解一下的,比如一些框架的底層實現。

 

“我不知道我有多喜歡你,但如果是去見你,我一定用跑的。”