在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程) 。用個比較通俗的好比,任何一個守護線程都是整個JVM中全部非守護線程的保姆:只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就所有工做;只有當最後一個非守護線程結束時,守護線程隨着JVM一同結束工做。Daemon的做用是爲其餘線程的運行提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。User 和Daemon二者幾乎沒有區別,惟一的不一樣之處就在於虛擬機的離開:若是 User Thread已經所有退出運行了,只剩下Daemon Thread存在了,虛擬機也就退出了。 由於沒有了被守護者,Daemon也就沒有工做可作了,也就沒有繼續運行程序的必要了。值得一提的是,守護線程並不是只有虛擬機內部提供,用戶在編寫程序時也能夠本身設置守護線程。下面的方法就是用來設置守護線程的。
html
Thread daemonTread = new Thread(); // 設定 daemonThread 爲 守護線程,default false(非守護線程) daemonThread.setDaemon(true); // 驗證當前線程是否爲守護線程,返回 true 則爲守護線程 daemonThread.isDaemon();
這裏有幾點須要注意: java
由於你不可能知道在全部的User完成以前,Daemon是否已經完成了預期的服務任務。一旦User退出了,可能大量數據尚未來得及讀入或寫出,計算任務也可能屢次運行結果不同。這對程序是毀滅性的。形成這個結果理由已經說過了:一旦全部User Thread離開了,虛擬機也就退出運行了。web
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(); //開始執行分進程 } }
//運行結果:文件daemon.txt中沒有"daemon"字符串,字符串並無寫入指定文件。緣由也很簡單,直到主線程完成,守護線程仍處於1秒的阻塞狀態。這個時候主線程很快就運行完了,虛擬機退出,Daemon中止服務,輸出操做天然失敗了。服務器
public class Test { public static void main(String args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true); // 設置爲守護線程 t2.start(); t1.start(); } }
class MyCommon extends Thread { public void run(){ for (int i = 0; i < 5; i++){ System.out.println("線程1第" + i + "次執行!"); try{ Thread.sleep(7); }catch(InterruptedException e){ e.printStackTrace(); } } } }
class MyDaemon implements Runnable { @Override public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("後臺線程第" + i + "次執行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
後臺線程第0次執行! 線程1第0次執行! 線程1第1次執行! 後臺線程第1次執行! 後臺線程第2次執行! 線程1第2次執行! 線程1第3次執行! 後臺線程第3次執行! 線程1第4次執行! 後臺線程第4次執行! 後臺線程第5次執行! 後臺線程第6次執行! 後臺線程第7次執行! Process finished with exit code 0
從上面的執行結果能夠看出: 前臺線程是保證執行完畢的,後臺線程尚未執行完畢就退出了。
多線程
實際上:JRE判斷程序是否執行結束的標準是全部的前臺執線程行完畢了,而無論後臺線程的狀態,所以,在使用後臺線程時候必定要注意這個問題。 併發
補充說明:app
垃圾回收線程就是一個經典的守護線程,當咱們的程序中再也不有任何運行的Thread,程序就不會再產生垃圾,垃圾回收器也就無事可作,因此當垃圾回收線程是JVM上僅剩的線程時,垃圾回收線程會自動離開。它始終在低級別的狀態中運行,用於實時監控和管理系統中的可回收資源。
生命週期:守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且週期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴於終端,可是依賴於系統,與系統"同生共死」。那Java的守護線程是什麼樣子的呢。當JVM中全部的線程都是守護線程的時候,JVM就能夠退出了;若是還有一個或以上的非守護線程則JVM不會退出。
實際應用例子:在使用長鏈接的comet服務端推送技術中,消息推送線程設置爲守護線程,服務於ChatServlet的servlet用戶線程,在servlet的init啓動消息線程,servlet一旦初始化後,一直存在服務器,servlet摧毀後,消息線程自動退出.容器收到一個Servlet請求,調度線程從線程池中選出一個工做者線程,將請求傳遞給該工做者線程,而後由該線程來執行Servlet的 service方法。當這個線程正在執行的時候,容器收到另一個請求,調度線程一樣從線程池中選出另外一個工做者線程來服務新的請求,容器並不關心這個請求是否訪問的是同一個Servlet.當容器同時收到對同一個Servlet的多個請求的時候,那麼這個Servlet的service()方法將在多線程中併發執行。
Servlet容器默認採用單實例多線程的方式來處理請求,這樣減小產生Servlet實例的開銷,提高了對請求的響應時間,對於Tomcat能夠在server.xml中經過<Connector>元素設置線程池中線程的數目。如圖:
ide
爲何要用守護線程?咱們知道靜態變量是ClassLoader級別的,若是Web應用程序中止,這些靜態變量也會從JVM中清除。可是線程則是JVM級別的,若是你在Web 應用中啓動一個線程,這個線程的生命週期並不會和Web應用程序保持同步。也就是說,即便你中止了Web應用,這個線程依舊是活躍的。正是由於這個很隱晦的問題,因此不少有經驗的開發者不太同意在Web應用中私自啓動線程。
若是咱們手工使用JDK Timer(Quartz的Scheduler),在Web容器啓動時啓動Timer,當Web容器關閉時,除非你手工關閉這個Timer,不然Timer中的任務還會繼續運行!下面經過一個小例子來演示這個「詭異」的現象,咱們經過ServletContextListener在Web容器啓動時建立一個Timer並週期性地運行一個任務:
spa
import java.util.Date; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class StartCycleRunTask implements ServletContextListener{ private Timer timer; public void contextDestroyed(ServletContextEvent arg0){ // ②該方法在Web容器關閉時執行 System.out.println("Web應用程序啓動關閉..."); } public void contextInitialized(ServletContextEvent arg0){ //②在Web容器啓動時自動執行該方法 System.out.println("Web應用程序啓動..."); timer = new Timer();//②-1:建立一個Timer,Timer內部自動建立一個背景線程 TimerTask task = new SimpleTimerTask(); timer.schedule(task, 1000L, 5000L); //②-2:註冊一個5秒鐘運行一次的任務 } } class SimpleTimerTask extends TimerTask{//③任務 private int count; public void run(){ System.out.println((++count)+"execute task..."+(new Date())); } }
在web.xml中聲明這個Web容器監聽器:<?xml version="1.0" encoding="UTF-8"?>
線程
<web-app> … <listener> <listener-class>com.baobaotao.web.StartCycleRunTask</listener-class> </listener> </web-app>
在Tomcat中部署這個Web應用並啓動後,你將看到任務每隔5秒鐘執行一次。 運行一段時間後,登陸Tomcat管理後臺,將對應的Web應用(chapter13)關閉。 轉到Tomcat控制檯,你將看到雖然Web應用已經關閉,但Timer任務還在我行我素地執行如故——舞臺已經拆除,戲子繼續表演: 咱們能夠經過改變清單StartCycleRunTask的代碼,在contextDestroyed(ServletContextEvent arg0)中添加timer.cancel()代碼,在Web容器關閉後手工中止Timer來結束任務。Spring 爲JDK Timer和Quartz Scheduler所提供的TimerFactoryBean和SchedulerFactoryBean可以和Spring容器的生命週期關聯,在 Spring容器啓動時啓動調度器,而在Spring容器關閉時,中止調度器。因此在Spring中經過這兩個FactoryBean配置調度器,再從 Spring IoC中獲取調度器引用進行任務調度將不會出現這種Web容器關閉而任務依然運行的問題。而若是你在程序中直接使用Timer或Scheduler,如不 進行額外的處理,將會出現這一問題。