Java 中的线程分为两类,分别为 daemon 线程(守护线程)和 user 线程(用户线程)。在 JVM 启动时会调用 main 函数,main 函数所在的线程就是一个用户线程,其实在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM 会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响 JVM 的退出。言外之意,只要有一个用户线程还没结束,正常情况下 JVM 就不会退出。

    那么在 Java 中如何创建一个守护线程?代码如下。

    1. public static void mainString[] args {
    2. Thread daemonThread = new Threadnew Runnable() {
    3. public void run() {
    4. }
    5. });
    6. //设置为守护线程
    7. daemonThread.setDaemontrue);
    8. daemonThread.start();
    9. }

    只需要设置线程的 daemon 参数为 true 即可。

    下面通过例子来理解用户线程与守护线程的区别。首先看下面的代码。

    1. public static void mainString[] args {
    2. Thread thread = new Threadnew Runnable() {
    3. public void run() {
    4. for(; ; ){}
    5. }
    6. });
    7. //启动子线程
    8. thread.start();
    9. System.out.print("main thread is over");
    10. }

    输出结果如下。

    守护线程与用户线程 - 图1

    如上代码在 main 线程中创建了一个 thread 线程,在 thread 线程里面是一个无限循环。从运行代码的结果看,main 线程已经运行结束了,那么 JVM 进程已经退出了吗?在 IDE 的输出结果右上侧的红色方块说明,JVM 进程并没有退出。另外,在 mac 上执行 jps 会输出如下结果。

    守护线程与用户线程 - 图2

    这个结果说明了当父线程结束后,子线程还是可以继续存在的,也就是子线程的生命周期并不受父线程的影响。这也说明了在用户线程还存在的情况下 JVM 进程并不会终止。那么我们把上面的 thread 线程设置为守护线程后,再来运行看看会有什么结果:

    1. //设置为守护线程
    2. thread.setDaemontrue);
    3. //启动子线程
    4. thread.start();

    输出结果如下。

    守护线程与用户线程 - 图3

    在启动线程前将线程设置为守护线程,执行后的输出结果显示,JVM 进程已经终止了,执行 ps -eaf |grep java 也看不到 JVM 进程了。在这个例子中,main 函数是唯一的用户线程,thread 线程是守护线程,当 main 线程运行结束后,JVM 发现当前已经没有用户线程了,就会终止 JVM 进程。由于这里的守护线程执行的任务是一个死循环,这也说明了如果当前进程中不存在用户线程,但是还存在正在执行任务的守护线程,则 JVM 不等守护线程运行完毕就会结束 JVM 进程。

    main 线程运行结束后,JVM 会自动启动一个叫作 DestroyJavaVM 的线程,该线程会等待所有用户线程结束后终止 JVM 进程。下面通过简单的 JVM 代码来证明这个结论。

    翻看 JVM 的代码,能够发现,最终会调用到 JavaMain 这个 C 函数。

    1. int JNICALL
    2. JavaMainvoid _args
    3. {
    4. ...
    5. //执行 Java 中的 main 函数
    6. (*env)->CallStaticVoidMethodenv, mainClass, mainID, mainArgs);
    7. //main 函数返回值
    8. ret = (*env)->ExceptionOccurredenv == NULL 0 : 1
    9. //等待所有非守护线程结束,然后销毁 JVM 进程
    10. LEAVE();
    11. }

    LEAVE 是 C 语言里面的一个宏定义,具体定义如下。

    1. #define LEAVE() \
    2. do { \
    3. if ((*vm)->DetachCurrentThread(vm) ! = JNI_OK) { \
    4. JLI_ReportErrorMessage(JVM_ERROR2); \
    5. ret = 1; \
    6. } \
    7. if (JNI_TRUE) { \
    8. (*vm)->DestroyJavaVM(vm); \
    9. return ret; \
    10. } \
    11. } while (JNI_FALSE)

    该宏的作用是创建一个名为 DestroyJavaVM 的线程,来等待所有用户线程结束。

    在 Tomcat 的 NIO 实现 NioEndpoint 中会开启一组接受线程来接受用户的连接请求,以及一组处理线程负责具体处理用户请求,那么这些线程是用户线程还是守护线程呢?下面我们看一下 NioEndpoint 的 startInternal 方法。

    1. public void startInternal() throws Exception {
    2. if (! running) {
    3. running = true
    4. paused = false
    5. ...
    6. //创建处理线程
    7. pollers = new Poller[getPollerThreadCount()];
    8. for int i=0; i<pollers.length; i++) {
    9. pollers[i] = new Poller();
    10. Thread pollerThread = new Thread(pollers[i], getName() +
    11. 「-ClientPoller-」+i);
    12. pollerThread.setPrioritythreadPriority);
    13. pollerThread.setDaemontrue); //声明为守护线程
    14. pollerThread.start();
    15. }
    16. //启动接受线程
    17. startAcceptorThreads();
    18. }
    19. protected final void startAcceptorThreads() {
    20. int count = getAcceptorThreadCount();
    21. acceptors = new Acceptor[count];
    22. for int i = 0 i < count i++) {
    23. acceptors[i] = createAcceptor();
    24. String threadName = getName() + 「-Acceptor-」 + i
    25. acceptors[i].setThreadNamethreadName);
    26. Thread t = new Threadacceptors[i], threadName);
    27. t.setPriority(getAcceptorThreadPriority());
    28. t.setDaemon(getDaemon()); //设置是否为守护线程,默认为守护线程
    29. t.start();
    30. }
    31. }
    32. private boolean daemon = true
    33. public void setDaemonboolean b { daemon = b }
    34. public boolean getDaemon() { return daemon }

    在如上代码中,在默认情况下,接受线程和处理线程都是守护线程,这意味着当 tomcat 收到 shutdown 命令后并且没有其他用户线程存在的情况下 tomcat 进程会马上消亡,而不会等待处理线程处理完当前的请求。

    总结:如果你希望在主线程结束后 JVM 进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线程结束后子线程继续工作,等子线程结束后再让 JVM 进程结束,那么就将子线程设置为用户线程。