- Java并發(fā)編程之美
- 翟陸續(xù) 薛賓田
- 1426字
- 2019-07-25 11:54:01
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è)置為用戶線程。
- Go Web編程
- C#高級編程(第10版) C# 6 & .NET Core 1.0 (.NET開發(fā)經(jīng)典名著)
- 玩轉(zhuǎn)Scratch少兒趣味編程
- JavaScript 網(wǎng)頁編程從入門到精通 (清華社"視頻大講堂"大系·網(wǎng)絡(luò)開發(fā)視頻大講堂)
- 實戰(zhàn)Java程序設(shè)計
- Python Geospatial Development(Second Edition)
- Mastering Yii
- Learning Laravel 4 Application Development
- Python面向?qū)ο缶幊蹋簶?gòu)建游戲和GUI
- jQuery Mobile移動應(yīng)用開發(fā)實戰(zhàn)(第3版)
- Java零基礎(chǔ)實戰(zhàn)
- OpenMP核心技術(shù)指南
- Android Game Programming by Example
- 可視化H5頁面設(shè)計與制作:Mugeda標準教程
- Improving your Penetration Testing Skills