在正式理解這個概念前,先把 守護線程 與 守護進程 這二個極其相似的說法區分開,守護進程通常是為了防止某些應用因各種意外原因退出,而在后臺獨立運行的系統服務或應用程序。 比如:我們開發了一個郵件發送程序,一直不停的監視隊列池,發現有待發送的郵件,就將其發送出去。如果這個程序掛了(或被人誤操作關了),郵件就不發出去了,為了防止這種情況,再開發一個類似windows 系統服務的應用,常駐后臺,監制這個郵件發送程序是否在運行,如果沒運行,則自動將其啟動。
而我們今天說的java中的守護線程(Daemon Thread) 指的是一類特殊的Thread,其優先級特別低(低到甚至可以被JVM自動終止),通常這類線程用于在空閑時做一些資源清理類的工作,比如GC線程,如果JVM中所有非守護線程(即:常規的用戶線程)都結束了,守護線程會被JVM中止,想想其實也挺合理,沒有任何用戶線程了,自然也不會有垃圾對象產生,GC線程也沒必要存在了。
實際開發中,也可以手動將線程設置為Daemon Thread,只有一個限制:必須在線程的start方法設置,見下面的示例:
package test; public class Program { public static void main(String[] args) { TestThread t1 = new TestThread(); t1.setDaemon(true); t1.start(); } private static class TestThread extends Thread { public void run() { System.out.println("test"); } } }
由于t1設置成Daemon Thread了,運行后,main進程馬上就結束,此時沒有用戶進程在運行,守護進程默認是不執行的,因此運行后,沒有任何輸出結果,符合我們剛才的解釋。
注:在idea等集成IDE環境下測試時,如果多次點擊Run按鈕,可能會發現第二次運行時,偶爾也會輸出test,估計是ide里上次運行后的java進程并未完全退出,可以手動把windows進程中的所有java.exe進程干掉再測試。
如果把t1.setDaemon(true);這一行注釋掉,就會輸出test了。
另外,如果把main函數最后加一行阻塞的代碼,比如:
public static void main(String[] args) throws IOException { TestThread t1 = new TestThread(); t1.setDaemon(true); t1.start(); System.in.read(); }
加了一行System.in.read()后,再運行,會發現test會輸出,這是因為main這個用戶線程被阻塞了,JVM發現有用戶進程在運行,守護進程才能機會被執行。
再來一個復雜點的示例:
假設有二個線程,一個是常規的用戶線程,不停寫入日志,另一個是守護線程,在空閑時清理日志(僅保留最近的5條日志)
package test; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class Program { private static int queueCapacity = 10; private static BlockingQueue<String> logQueue = new ArrayBlockingQueue<String>(queueCapacity); public static void main(String[] args) throws IOException { LogWriter writer = new LogWriter(); LogCleaner cleaner = new LogCleaner(); cleaner.setDaemon(true); writer.start(); cleaner.start(); } /** * 模擬不停寫日志(直到隊列寫滿) */ private static class LogWriter extends Thread { public void run() { for (int i = 0; i < queueCapacity; i++) { try { logQueue.put("" + i); System.out.println("日志已寫入,當前日志內容:" + logQueue); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 模擬在空閑時清理日志(僅保留5條日志) */ private static class LogCleaner extends Thread { public void run() { while (true) { if (logQueue.size() > 5) { try { logQueue.take(); System.out.println("多余日志被清理,當前日志內容:" + logQueue); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
運行結果:
1 日志已寫入,當前日志內容:[0] 2 日志已寫入,當前日志內容:[0, 1] 3 日志已寫入,當前日志內容:[0, 1, 2] 4 日志已寫入,當前日志內容:[0, 1, 2, 3] 5 日志已寫入,當前日志內容:[0, 1, 2, 3, 4] 6 日志已寫入,當前日志內容:[0, 1, 2, 3, 4, 5] 7 多余日志被清理,當前日志內容:[1, 2, 3, 4, 5] 8 日志已寫入,當前日志內容:[1, 2, 3, 4, 5, 6] 9 多余日志被清理,當前日志內容:[2, 3, 4, 5, 6] 10 日志已寫入,當前日志內容:[2, 3, 4, 5, 6, 7] 11 多余日志被清理,當前日志內容:[3, 4, 5, 6, 7] 12 日志已寫入,當前日志內容:[3, 4, 5, 6, 7, 8] 13 多余日志被清理,當前日志內容:[4, 5, 6, 7, 8] 14 日志已寫入,當前日志內容:[4, 5, 6, 7, 8, 9] 15 多余日志被清理,當前日志內容:[5, 6, 7, 8, 9]
參考文章:
文章列表