官术网_书友最值得收藏!

1.10 守護線程與用戶線程

Java中的線程分為兩類,分別為daemon線程(守護線程)和user線程(用戶線程)。在JVM啟動時會調(diào)用main函數(shù),main函數(shù)所在的線程就是一個用戶線程,其實在JVM內(nèi)部同時還啟動了好多守護線程,比如垃圾回收線程。那么守護線程和用戶線程有什么區(qū)別呢?區(qū)別之一是當最后一個非守護線程結(jié)束時,JVM會正常退出,而不管當前是否有守護線程,也就是說守護線程是否結(jié)束并不影響JVM的退出。言外之意,只要有一個用戶線程還沒結(jié)束,正常情況下JVM就不會退出。

那么在Java中如何創(chuàng)建一個守護線程?代碼如下。

public static void main(String[] args) {
        Thread daemonThread = new Thread(new  Runnable() {
            public void run() {
            }
        });
        //設(shè)置為守護線程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }

只需要設(shè)置線程的daemon參數(shù)為true即可。

下面通過例子來理解用戶線程與守護線程的區(qū)別。首先看下面的代碼。

    public static void main(String[] args) {
        Thread thread = new Thread(new  Runnable() {
            public void run() {
                for(; ; ){}
            }
        });
        //啟動子線程
        thread.start();
        System.out.print("main thread is over");
    }

輸出結(jié)果如下。

如上代碼在main線程中創(chuàng)建了一個thread線程,在thread線程里面是一個無限循環(huán)。從運行代碼的結(jié)果看,main線程已經(jīng)運行結(jié)束了,那么JVM進程已經(jīng)退出了嗎?在IDE的輸出結(jié)果右上側(cè)的紅色方塊說明,JVM進程并沒有退出。另外,在mac上執(zhí)行jps會輸出如下結(jié)果。

這個結(jié)果說明了當父線程結(jié)束后,子線程還是可以繼續(xù)存在的,也就是子線程的生命周期并不受父線程的影響。這也說明了在用戶線程還存在的情況下JVM進程并不會終止。那么我們把上面的thread線程設(shè)置為守護線程后,再來運行看看會有什么結(jié)果:

        //設(shè)置為守護線程
        thread.setDaemon(true);
        //啟動子線程
        thread.start();

輸出結(jié)果如下。

在啟動線程前將線程設(shè)置為守護線程,執(zhí)行后的輸出結(jié)果顯示,JVM進程已經(jīng)終止了,執(zhí)行ps -eaf |grep java也看不到JVM進程了。在這個例子中,main函數(shù)是唯一的用戶線程,thread線程是守護線程,當main線程運行結(jié)束后,JVM發(fā)現(xiàn)當前已經(jīng)沒有用戶線程了,就會終止JVM進程。由于這里的守護線程執(zhí)行的任務(wù)是一個死循環(huán),這也說明了如果當前進程中不存在用戶線程,但是還存在正在執(zhí)行任務(wù)的守護線程,則JVM不等守護線程運行完畢就會結(jié)束JVM進程。

main線程運行結(jié)束后,JVM會自動啟動一個叫作DestroyJavaVM的線程,該線程會等待所有用戶線程結(jié)束后終止JVM進程。下面通過簡單的JVM代碼來證明這個結(jié)論。

翻看JVM的代碼,能夠發(fā)現(xiàn),最終會調(diào)用到JavaMain這個C函數(shù)。

int JNICALL
JavaMain(void * _args)
{
    ...
    //執(zhí)行Java中的main函數(shù)
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    //main函數(shù)返回值
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    //等待所有非守護線程結(jié)束,然后銷毀JVM進程
    LEAVE();
}

LEAVE是C語言里面的一個宏定義,具體定義如下。

#define LEAVE() \
    do { \
        if ((*vm)->DetachCurrentThread(vm) ! = JNI_OK) { \
            JLI_ReportErrorMessage(JVM_ERROR2); \
            ret = 1; \
        } \
        if (JNI_TRUE) { \
            (*vm)->DestroyJavaVM(vm); \
            return ret; \
        } \
    } while (JNI_FALSE)

該宏的作用是創(chuàng)建一個名為DestroyJavaVM的線程,來等待所有用戶線程結(jié)束。

在Tomcat的NIO實現(xiàn)NioEndpoint中會開啟一組接受線程來接受用戶的連接請求,以及一組處理線程負責具體處理用戶請求,那么這些線程是用戶線程還是守護線程呢?下面我們看一下NioEndpoint的startInternal方法。

public void startInternal() throws Exception {
      if (! running) {
            running = true;
            paused = false;
            ...
            //創(chuàng)建處理線程
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() +
                "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true); //聲明為守護線程
                pollerThread.start();
            }
            //啟動接受線程
            startAcceptorThreads();
        }
    protected final void startAcceptorThreads() {
        int count = getAcceptorThreadCount();
        acceptors = new Acceptor[count];
        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon()); //設(shè)置是否為守護線程,默認為守護線程
            t.start();
        }
    }
    private boolean daemon = true;
    public void setDaemon(boolean b) { daemon = b; }
    public boolean getDaemon() { return daemon; }

在如上代碼中,在默認情況下,接受線程和處理線程都是守護線程,這意味著當tomcat收到shutdown命令后并且沒有其他用戶線程存在的情況下tomcat進程會馬上消亡,而不會等待處理線程處理完當前的請求。

總結(jié):如果你希望在主線程結(jié)束后JVM進程馬上結(jié)束,那么在創(chuàng)建線程時可以將其設(shè)置為守護線程,如果你希望在主線程結(jié)束后子線程繼續(xù)工作,等子線程結(jié)束后再讓JVM進程結(jié)束,那么就將子線程設(shè)置為用戶線程。

主站蜘蛛池模板: 绥阳县| 宁津县| 湘潭市| 临安市| 通山县| 鄱阳县| 高平市| 白玉县| 仙游县| 文化| 阳西县| 邮箱| 昌邑市| 宜兰县| 永平县| 清丰县| 铜梁县| 鄢陵县| 恩平市| 曲沃县| 西贡区| 晋州市| 容城县| 斗六市| 富顺县| 蚌埠市| 托里县| 昌黎县| 班玛县| 灵宝市| 克拉玛依市| 正宁县| 南汇区| 靖远县| 通山县| 夹江县| 丹东市| 木里| 宜都市| 海安县| 静乐县|