您的位置:首页 > 编程语言 > Java开发

从 JVM 视角看看 Java 守护线程

2019-10-16 16:07 791 查看

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 源代码构造方法里面就可以知道,代码如下所示。郑州治疗不孕不育哪里好:http://mobile.xbzztj.com/

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

运行结果如下。郑州女性不孕不育医院:http://wapjbk.39.net/yiyuanzaixian/zztjyy/

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

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

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

总结

总结一下这篇文章讲解的点,一个是线程被分为 2 种类型,一种是用户线程,另一种是守护线程;如果要把线程设置为守护线程,需要在线程调用

start()
方法前设置 
daemon
 属性;还有从 JVM 源码角度分析为什么当用户线程都执行完的时候,JVM 会自动退出。接着讲解了守护线程有继承性,父线程是守护线程,那么子线程默认就是守护线程;另外对一些书籍和资料所说的 守护线程优先级比用户线程低 提出自己的疑问,并希望有了解的朋友能帮忙解答。http://www.chacha8.cn/detail/1132398214.html


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: