Java 多线程系列第 7 篇。java
这篇咱们来说讲线程的另外一个特性:守护线程 or 用户线程?设计模式
咱们先来看看 Thread.setDaemon()
方法的注释,以下所示。多线程
1. Marks this thread as either a daemon thread or a user thread.并发
里面提到了 3 点信息,一一来作下解释:异步
把 Java 线程分红 2 类,一类是用户线程,也就是咱们建立线程时,默认的一类线程,属性 daemon = false
;另外一类是守护线程,当咱们设置 daemon = true
时,就是这类线程。ide
二者的通常关系是:用户线程就是运行在前台的线程,守护线程就是运行在后台的线程,通常状况下,守护线程是为用户线程提供一些服务。好比在 Java 中,咱们常说的 GC 内存回收线程就是守护线程。学习
上面第二点翻译过来是:当全部用户线程都执行完,只存在守护线程在运行时,JVM 就退出。看了网上资料以及一些书籍,全都有这句话,可是也都只是有这句话,没有讲明是为啥,好像这句话就成了定理,不须要证实的样子。既然咱最近搭建了 JVM Debug 环境,那就得来查个究竟。(查得好辛苦,花了好久的时间才查出来)测试
咱们看到 JVM 源码 thread.cpp
文件,这里是实现线程的代码。咱们经过上面那句话,说明是有一个地方监测着当前非守护线程的数量,否则怎么知道如今只剩下守护线程呢?颇有多是在移除线程的方法里面,跟着这个思路,咱们看看该文件的 remove()
方法。代码以下。ui
/**
* 移除线程 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
文件下。this
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()
会等待全部非守护线程都执行完,里面是用变量 numberofnondaemonthreads 统计非守护线程的数量,这个变量在新增线程和删除线程时会作增减操做。
另外衍生一点就是:当 JVM 退出时,全部还存在的守护线程会被抛弃,既不会执行 finally 部分代码,也不会执行 stack unwound 操做(也就是也不会 catch 异常)。这个很明显,JVM 都退出了,守护线程天然退出了,固然这是守护线程的一个特性。
这个比较好理解,就是线程是用户线程仍是守护线程,在线程还未启动时就得肯定。在调用 start()
方法以前,还只是个对象,没有映射到 JVM 中的线程,这个时候能够修改 daemon
属性,调用 start()
方法以后,JVM 中就有一个线程映射这个线程对象,因此不能作修改了。
这个咱就不用写代码来验证了,直接看 Thread 源代码构造方法里面就能够知道,代码以下所示。
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...省略一堆代码
this.daemon = parent.isDaemon();
...省略一堆代码
}复制代码
看到不少书籍和资料都这么说,我也很怀疑。因此写了下面代码来测试是否是守护线程优先级比用户线程低?
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 了
了解Java线程优先级,更要知道对应操做系统的优先级,否则会踩坑
后台回复『设计模式』能够获取《一故事一设计模式》电子书
以为文章有用帮忙转发&点赞,多谢朋友们!
本文由博客一文多发平台 OpenWrite 发布!