firm,aqy-无线网络搭建、互联网工程师

Java 多线程系列第 7 篇。

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

咱们先来看看 Thread.sefirm,aqy-无线网络建立、互联网工程师tDaemon() 办法的注释,如下所示。

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 看护线程?

把 Jav安堂奈奈a 线程分红 2 类,一类是用户线程,也便是咱们创立线程时,默许的一类线程,特点 daemon = fal展业达人钱包se;另一类是看护线程,当咱们设置 daemon = true 时,便是这类线程。

两者的一般联系是:用户线程便是运转在前台的线程,看护线程便是运转在后台的线程,一般情况下,看护线程是为用户线程供给一些服务。比如在 Java 中,咱们常说的 GC 内存收回线程便是看护线程。

2. JVM 与用户线程共存亡

上面第二点翻译过来是:当一切用户线程都履行完,只存在看护Lori阿姨线程在运转时,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);
// 当时线程数firm,aqy-无线网络建立、互联网工程师减 1
_number_of_threads--;
if (!is_daemon) {
/**
* 非看护线程数量减 1
*/
_numbe多美娅r_of_non_daemon_threads--;
/**
* 当非看护线程数量为 1 时,唤醒在 destroy_vm() 办法firm,aqy-无线网络建立、互联网工程师等候的线ca4529程
*/
// Only one thread le饱满女性ft, 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 isfirm,aqy-无线网络建立、互联网工程师 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_t龙井说唱被关了几年erminated_value();
} // unlock Threads_lock
// Since Events::log uses a lock, we gr人体课ab 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 thefirm,aqy-无线网络建立、互联网工程师 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 s霍泊宏uspend-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 都退出了,看护线程天然退出了,当然这是看护线程的一个特性。

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

这个比较好了解,便是线程是用户线程仍是看护线程,在线程还未启动时就得确认。在调用 start() 办法之前,还仅仅个目标,没有映射到 JVM 中的线程,这个时分能够修正 daemon 特点,调用 start() 办法之后,JVM 中就有一个线程映射这个线程目标,所以不能做修正了。

其他的香港红灯区特性

1.看护线程特点承继自父线程

这个咱就不必写代码来验证了,直接看 Thread 源代码结构办法里边就能够知道,代码如下所示。

private Thread(ThreadGroup g, Runnable targefirm,aqy-无线网络建立、互联网工程师t, 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 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);
} c萝莉圣片atch (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 会主动退出。接着解说了看护线程有承继性,父线程是看护线程,那么子线程默许便是看护线程;别的对一些书本和材料所说的 看护线程优先级比用户线程低 提出自己的疑问,并期望有了解的朋友能帮助回答。

假如觉得这篇文章看了有收成,费事点个在看,支撑一下,原创不易。

引荐阅览

全网最新最简略的 OpenJDK13 代码编译

写了那么多年 Java 代码,总算 debug 到 JVM 了

觉得文章不错的朋友帮助点个赞,重视 LieBrother,第一时刻看更多文章firm,aqy-无线网络建立、互联网工程师。

点击展开全文

上一篇:

下一篇:

相关推荐

充电桩,妹子图-无线网络搭建、互联网工程师

2019年12月28日 136 0