从 JVM 视角看看 Java 守护线程

Java 多线程系列第 7 篇。

这篇我们来讲讲线程的另一个特性:守护线程 or 用户线程?

我们先来看看 Thread.setDaemon() 方法的注释,如下所示。

  1. Marks this thread as either a daemon thread or a user thread.
  1. The Java Virtual Machine exits when the only threads running are all daemon threads.
  2. This method must be invoked before the thread is started.

里面提到了 3 点信息,一一来做下解释:

官方特性

1. 用户线程 or 守护线程?

把 Java 线程分成 2 类,一类是用户线程,也就是我们创建线程时,默认的一类线程,属性 daemon = false;另一类是守护线程,当我们设置 daemon = true 时,就是这类线程。

两者的一般关系是:用户线程就是运行在前台的线程,守护线程就是运行在后台的线程,一般情况下,守护线程是为用户线程提供一些服务。比如在 Java 中,我们常说的 GC 内存回收线程就是守护线程。

2. JVM 与用户线程共存亡

上面第二点翻译过来是:当所有用户线程都执行完,只存在守护线程在运行时,JVM 就退出。看了网上资料以及一些书籍,全都有这句话,但是也都只是有这句话,没有讲明是为啥,好像这句话就成了定理,不需要证明的样子。既然咱最近搭建了 JVM Debug 环境,那就得来查个究竟。(查得好辛苦,花了很久的时间才查出来)

我们看到 JVM 源码 thread.cpp 文件,这里是实现线程的代码。我们通过上面那句话,说明是有一个地方监测着当前非守护线程的数量,不然怎么知道现在只剩下守护线程呢?很有可能是在移除线程的方法里面,跟着这个思路,我们看看该文件的 remove() 方法。代码如下。

/**
 * 移除线程 p
 */
void Threads::remove(JavaThread* p, bool is_daemon) {

  // Reclaim the ObjectMonitors from the omInUseList and omFreeList of the moribund thread.
  ObjectSynchronizer::omFlush(p);

  /**
   * 创建一个监控锁对象 ml
   */
  // Extra scope needed for Thread_lock, so we can check
  // that we do not remove thread without safepoint code notice
  { MonitorLocker ml(Threads_lock);

    assert(ThreadsSMRSupport::get_java_thread_list()->includes(p), "p must be present");

    // Maintain fast thread list
    ThreadsSMRSupport::remove_thread(p);

    // 当前线程数减 1
    _number_of_threads--;
    if (!is_daemon) {
        /**
         * 非守护线程数量减 1
         */
      _number_of_non_daemon_threads--;

      /**
       * 当非守护线程数量为 1 时,唤醒在 destroy_vm() 方法等待的线程
       */
      // Only one thread left, do a notify on the Threads_lock so a thread waiting
      // on destroy_vm will wake up.
      if (number_of_non_daemon_threads() == 1) {
        ml.notify_all();
      }
    }
    /**
     * 移除掉线程
     */
    ThreadService::remove_thread(p, is_daemon);

    // Make sure that safepoint code disregard this thread. This is needed since
    // the thread might mess around with locks after this point. This can cause it
    // to do callbacks into the safepoint code. However, the safepoint code is not aware
    // of this thread since it is removed from the queue.
    p->set_terminated_value();
  } // unlock Threads_lock

  // Since Events::log uses a lock, we grab it outside the Threads_lock
  Events::log(p, "Thread exited: " INTPTR_FORMAT, p2i(p));
}

我在里面加了一些注释,可以发现,果然是我们想的那样,里面有记录着非守护线程的数量,而且当非守护线程为 1 时,就会唤醒在 destory_vm() 方法里面等待的线程,我们确认已经找到 JVM 在非守护线程数为 1 时会触发唤醒监控 JVM 退出的线程代码。紧接着我们看看 destory_vm() 代码,同样是在 thread.cpp 文件下。

bool Threads::destroy_vm() {
  JavaThread* thread = JavaThread::current();

#ifdef ASSERT
  _vm_complete = false;
#endif
  /**
   * 等待自己是最后一个非守护线程条件
   */
  // Wait until we are the last non-daemon thread to execute
  { MonitorLocker nu(Threads_lock);
    while (Threads::number_of_non_daemon_threads() > 1)
        /**
         * 非守护线程数大于 1,则一直等待
         */
      // This wait should make safepoint checks, wait without a timeout,
      // and wait as a suspend-equivalent condition.
      nu.wait(0, Mutex::_as_suspend_equivalent_flag);
  }

  /**
   * 下面代码是关闭 VM 的逻辑
   */
  EventShutdown e;
  if (e.should_commit()) {
    e.set_reason("No remaining non-daemon Java threads");
    e.commit();
  }
  ...... 省略余下代码
}

我们这里看到当非守护线程数量大于 1 时,就一直等待,直到剩下一个非守护线程时,就会在线程执行完后,退出 JVM。这时候又有一个点需要定位,什么时候调用 destroy_vm() 方法呢?还是通过查看代码以及注释,发现是在 main() 方法执行完成后触发的。

java.c 文件的 JavaMain() 方法里面,最后执行完调用了 LEAVE() 方法,该方法调用了 (*vm)->DestroyJavaVM(vm); 来触发 JVM 退出,最终调用 destroy_vm() 方法。

#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)

所以我们也知道了,为啥 main 线程可以比子线程先退出?虽然 main 线程退出前调用了 destroy_vm() 方法,但是在 destroy_vm() 方法里面等待着非守护线程执行完,子线程如果是非守护线程,则 JVM 会一直等待,不会立即退出。

我们对这个点总结一下:Java 程序在 main 线程执行退出时,会触发执行 JVM 退出操作,但是 JVM 退出方法 destroy_vm() 会等待所有非守护线程都执行完,里面是用变量 number_of_non_daemon_threads 统计非守护线程的数量,这个变量在新增线程和删除线程时会做增减操作

另外衍生一点就是:当 JVM 退出时,所有还存在的守护线程会被抛弃,既不会执行 finally 部分代码,也不会执行 stack unwound 操作(也就是也不会 catch 异常)。这个很明显,JVM 都退出了,守护线程自然退出了,当然这是守护线程的一个特性。

3. 是男是女?生下来就注定了

这个比较好理解,就是线程是用户线程还是守护线程,在线程还未启动时就得确定。在调用 start() 方法之前,还只是个对象,没有映射到 JVM 中的线程,这个时候可以修改 daemon 属性,调用 start() 方法之后,JVM 中就有一个线程映射这个线程对象,所以不能做修改了。

其他的特性

1.守护线程属性继承自父线程

这个咱就不用写代码来验证了,直接看 Thread 源代码构造方法里面就可以知道,代码如下所示。

private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
   ...省略一堆代码
    this.daemon = parent.isDaemon();
   ...省略一堆代码
}

2.守护线程优先级比用户线程低

看到很多书籍和资料都这么说,我也很怀疑。所以写了下面代码来测试是不是守护线程优先级比用户线程低?

public class TestDaemon {
    static AtomicLong daemonTimes = new AtomicLong(0);
    static AtomicLong userTimes = new AtomicLong(0);

    public static void main(String[] args) {
        int count = 2000;
        List<MyThread> threads = new ArrayList<>(count);
        for (int i = 0; i < count; i ++) {
            MyThread userThread = new MyThread();
            userThread.setDaemon(false);
            threads.add(userThread);

            MyThread daemonThread = new MyThread();
            daemonThread.setDaemon(true);
            threads.add(daemonThread);
        }

        for (int i = 0; i < count; i++) {
            threads.get(i).start();
        }

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("daemon 统计:" + daemonTimes.get());
        System.out.println("user 统计:" + userTimes.get());
        System.out.println("daemon 和 user 相差时间:" + (daemonTimes.get() - userTimes.get()) + "ms");

    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            if (this.isDaemon()) {
                daemonTimes.getAndAdd(System.currentTimeMillis());
            } else {
                userTimes.getAndAdd(System.currentTimeMillis());
            }
        }
    }
}

运行结果如下。

结果1:
daemon 统计:1570785465411405
user 统计:1570785465411570
daemon 和 user 相差时间:-165ms

结果2:
daemon 统计:1570786615081403
user 统计:1570786615081398
daemon 和 user 相差时间:5ms

是不是很惊讶,居然相差无几,但是这个案例我也不能下定义说:守护线程和用户线程优先级是一样的。看了 JVM 代码也没找到守护线程优先级比用户线程低,这个点还是保持怀疑,有了解的朋友可以留言说一些,互相交流学习。

总结

总结一下这篇文章讲解的点,一个是线程被分为 2 种类型,一种是用户线程,另一种是守护线程;如果要把线程设置为守护线程,需要在线程调用start()方法前设置 daemon 属性;还有从 JVM 源码角度分析为什么当用户线程都执行完的时候,JVM 会自动退出。接着讲解了守护线程有继承性,父线程是守护线程,那么子线程默认就是守护线程;另外对一些书籍和资料所说的 守护线程优先级比用户线程低 提出自己的疑问,并希望有了解的朋友能帮忙解答。

如果觉得这篇文章看了有收获,麻烦点个在看,支持一下,原创不易。

推荐阅读

写了那么多年 Java 代码,终于 debug 到 JVM 了

原创 | 全网最新最简单的 openjdk13 代码编译

了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

线程最最基础的知识

老板叫你别阻塞了

吃个快餐都能学到串行、并行、并发

泡一杯茶,学一学同异步

进程知多少?

设计模式看了又忘,忘了又看?

后台回复『设计模式』可以获取《一故事一设计模式》电子书

觉得文章有用帮忙转发&点赞,多谢朋友们!

本文由博客一文多发平台 OpenWrite 发布!

原文地址:https://www.cnblogs.com/liebrother/p/11675467.html

时间: 2024-08-04 00:28:43

从 JVM 视角看看 Java 守护线程的相关文章

JAVA守护线程与用户线程的区别

public class DaemonTest { public static void main(String[] args) { new WorkerThread().start(); try { Thread.sleep(7500); } catch (InterruptedException e) {} System.out.println("Main Thread ending") ; } } class WorkerThread extends Thread { publi

java 守护线程整理

java中finally语句不走的可能存在system.exit(0)与守护线程 线程sleep采用TimeUnit类 设定线程的名字thread.getcurrentThread().setName() 设定守护线程thread.getcurrentThread().setDaemon(true) http://www.cnblogs.com/diyingyun/archive/2011/12/04/2275268.html 守护线程在没有用户线程可服务时自动离开,在 Java中比较特殊的线程

Java 守护线程(Daemon) 例子

Java  守护线程(Daemon) 示例 本文由 TonySpark 翻译自 Javarevisited.转载请参见文章末尾的要求. 当我们在Java中创建一个线程,缺省状态下它是一个User线程,如果该线程运行,JVM不会终结该程序.当一个线被标记为守护线程,JVM不会等待其结束,只要所有用户(User)线程都结束,JVM将终结程序及相关联的守护线程. Java中可以用 Thread.setDaemon(true) 来创建一个守护线程.咱们看一个Java中有关守护线程的例子. 1 publi

转:JAVA守护线程

原文地址:https://www.cnblogs.com/wxgblogs/p/5417503.html 详细内容看原文~  ,写的挺好的 在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) .用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作:只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作.Daemon的作用是为其他线程的运行提供便利服

Java守护线程概述

Java的线程分为两种:User Thread(用户线程).DaemonThread(守护线程). 只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作:只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者. User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thre

java守护线程的理解

package daemonThread; /*setDaemon(true)方法将线程设置为守护线程,线程的Daemon默认值为false * 只要当前JVM实例中存在任何一个非守护线程没有结束,守护线程就在工作 * 当进程中不存在非守护线程,则守护线程随着JVM一同结束 * GC(垃圾回收器)就是一个守护线程 * 本例中main线程虽然先结束,但是testThread线程还在工作,所以只有当testThread线程也结束,才停止打印i */ class MyThread extends Th

JAVA - 守护线程(Daemon Thread)

转载自:http://www.cnblogs.com/luochengor/archive/2011/08/11/2134818.html 在Java中有两类线程:用户线程 (User Thread).守护线程 (Daemon Thread). 所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分.因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程.反过来说,只要任何非守护线

白话JAVA守护线程

OneCoder(苦逼Coder)原创,转载请务必注明出处: http://www.coderli.com/archives/daemon-thread-plain-words/ 关于“白话”:偶然想到的词,也许有一天能成为一个系列.目的就是用简洁,明快的语言来告诉您,我所知道的一切. Java中的线程分两类,用户线程和守护线程. Thread commonThread = new Thread("Common Thread"); 这样就是用户线程. Thread daemonThre

java守护线程

/** * 后台线程又叫做守护线程,通常是为了辅助其他线程而运行的线程,‘ * 后台线程不妨碍程序终止, * 一个程序中只要还有一个前台线程在执行,这个后台线程就不会终止. * 反之,如果所有前台线程都已经结束,无论是否还有后台线程在执行,这个进程都会结束. * 如果对某个线程对象在启动(调用start()方法)前调用setDeamon(true)方法,这个线程就会变成后台线程. * 创建一个无限循环后台线程,验证前台线程结束后,它也结束 */ package com.starain.threa