淺談Java守護線程

1、在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程) java

一、Daemon的做用是爲其餘線程的運行提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。web

二、User和Daemon二者幾乎沒有區別,惟一的不一樣之處就在於虛擬機的離開:若是 User Thread已經所有退出運行了,只剩下Daemon Thread存在了,虛擬機也就退出了。 由於沒有了被守護者,Daemon也就沒有工做可作了,也就沒有繼續運行程序的必要了。spring

三、用戶在編寫程序時能夠本身設置守護線程:tomcat

Thread daemonTread = new Thread();
 
  // 設定 daemonThread 爲 守護線程,default false(非守護線程)
 daemonThread.setDaemon(true);
 
 // 驗證當前線程是否爲守護線程,返回 true 則爲守護線程
 daemonThread.isDaemon();

注:服務器

(1) thread.setDaemon(true)必須在thread.start()以前設置,不然會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置爲守護線程。
(2) 在Daemon線程中產生的新線程也是Daemon的。 
(3) 不要認爲全部的應用均可以分配給Daemon來進行服務,好比讀寫操做或者計算邏輯。多線程

四、代碼實例併發

由於你不可能知道在全部的User完成以前,Daemon是否已經完成了預期的服務任務。一旦User退出了,可能大量數據尚未來得及讀入或寫出,計算任務也可能屢次運行結果不同。這對程序是毀滅性的。形成這個結果理由已經說過了:一旦全部User Thread離開了,虛擬機也就退出運行了。 spa

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestRunnable implements Runnable {
    public void run(){
        try {
            Thread.sleep(1000);
            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 static void main(String[] args) {
        Runnable tr = new TestRunnable();
        Thread thread = new Thread(tr);
        thread.setDaemon(true);
        thread.start();
    }
} 
//運行結果:文件daemon.txt中沒有"daemon"字符串。

可是若是設置成普通線程,daemon會寫入daemon.txt文件中.net

把輸入輸出邏輯包裝進守護線程,字符串並無寫入指定文件線程

五、緣由:直到主線程完成,守護線程仍處於1秒的阻塞狀態,主線程運行完成,虛擬機退出,daemon中止服務,守護線程中止運行。

代碼實例:

package reflect;

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();
    }
}
package reflect;

public class MyDaemon implements Runnable{
    public void run(){
        for (int i = 0; i < 1000; i++) {
            System.out.println("後臺線程第"+i+"次執行!");
            try {
                Thread.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package reflect;

public 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();
            }
        }
    }
}

運行結果:

從上面的執行結果能夠看出: 
前臺線程是保證執行完畢的,後臺線程尚未執行完畢就退出了。 

實際上:JRE判斷程序是否執行結束的標準是全部的前臺執線程行完畢了,而無論後臺線程的狀態,所以,在使用後臺線程時候必定要注意這個問題。 

2、補充說明:

一、定義:守護線程也稱服務線程,在沒有用戶線程可服務時會自動離開。

二、優先級:守護線程的優先級比較低,用於爲系統中的其它對象和線程提供服務。

三、設置:經過setDaemon(true)來設置守護線程。

四、example:垃圾回收線程就是一個經典的守護線程,當咱們的程序中再也不有任何運行的
Thread,程序就不會再產生垃圾,垃圾回收器也就無事可作,因此當垃圾回收線程是
JVM上僅剩的線程時,垃圾回收線程會自動離開。它始終在低級別的狀態中運行,用於
實時監控和管理系統中的可回收資源。

五、生命週期:守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端而且
週期性地執行某種任務或等待處理某些發生的事件。也就是
說守護線程不依賴於終端,可是依賴於系統,與系統「同生共死」。那Java的守護線程是
什麼樣子的呢。當JVM中全部的線程都是守護線程的時候,JVM就能夠退出了;若是還有一個或以上的非守護線程則JVM不會退出。

3、實際應用例子:

一、在使用長鏈接的comet服務端推送技術中,消息推送線程設置爲守護線程,服務於ChatServlet的servlet用戶線程,servlet的init啓動消息線程,servlet一旦初始化後,一直存在於服務器,servlet摧毀後,消息線程自動退出。

二、容器收到一個servlet請求,調度線程從線程池中選出一個工做者線程,而後由該線程來執行servlet的service方法。當這個線程正在執行的時候,容器收到另外一個請求,調度線程一樣從線程池中選出另外一個工做者線程來服務新的請求,容器並不關心這個線程是否訪問的是同一個servlet,當容器同時收到同一個servlet的多個請求的時候,name這個servlet的service方法將在多線程中併發執行。

三、servlet容器默認採用單例多線程的方式來處理請求,這樣減小產生servlet實例的開銷,提高了對請求的響應時間,對tomcat能夠在server.xml中經過<Connector>元素設置線程池中線程的數目。

 

4、爲何要用守護線程?

一、咱們知道靜態變量是classLoader級別的,若是web應用程序中止,這些靜態變量也會從JVM中清除。可是線程時JVM級別的,若是在web應用中啓動一個線程,這個線程的聲明週期並不會和web應用程序保持同步。就是說,即便中止了web應用,這個線程依舊是活躍的。正是由於這個隱晦的問題,因此不少有經驗的開發者不同意在web應用中私自啓動線程。

二、若是咱們手動使用JDK Timer,在web容器啓動時啓動Timer,當web容器關閉時,除非手動關閉這個timer,否者timer中的任務還會繼續執行。

三、下面經過一個小例子來演示這個「詭異」的現象,咱們經過ServletContextListener在Web容器啓動時建立一個Timer並週期性地運行一個任務:  

//代碼清單StartCycleRunTask:容器監聽器
package com.baobaotao.web;
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()));
    }
}

四、在Tomcat中部署這個Web應用並啓動後,你將看到任務每隔5秒鐘執行一次。 
運行一段時間後,登陸Tomcat管理後臺,將對應的Web應用(chapter13)關閉。 

五、轉到Tomcat控制檯,你將看到雖然Web應用已經關閉,但Timer任務還在我行我素地執行如故——舞臺已經拆除,戲子繼續表演: 

六、咱們能夠經過改變清單StartCycleRunTask的代碼,在contextDestroyed(ServletContextEvent arg0)中添加timer.cancel()代碼,在Web容器關閉後手工中止Timer來結束任務。

5、spring爲jdk timer和Quartz Scheduler提供的timerFactoryBean和ScherdulerFactoryBean可以和spring容器的生命週期關聯,在spring容器啓動時啓動調度器,而在spring容器關閉時,中止調度器。因此在spring中經過這兩個factoryBean配置調度器,再從spring IOC中獲取調度器引用進行任務調度將不會出現這種web容器關閉而任務依然運行的問題,而若是你在程序中直接使用timer或scheduler,如不進行額外的處理,將會出現相似的問題。

 

江疏影講Java@目錄

相關文章
相關標籤/搜索