今天在写单元测试时遇到一个问题,单元测试里调用异步函数,执行完毕后发现异步函数期望达成的结果和预期不一致,记录下分析过程。
Junit Test
@Test
void test(){
//jvm关闭钩子函数
//Runtime.getRuntime().addShutdownHook(new Thread(() -> log.info("钩子开始执行:{}, {}", Thread.currentThread().getName(), Thread.currentThread().isDaemon())));
log.info("{}-{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
Thread t = new Thread(()->{
log.info("{}-{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("子线程运行结束");
});
//关闭守护线程标记
//t.setDaemon(false);
t.start();
log.info("MAIN线程运行结束");
}
执行结果:
从执行结果看,我们期望的"子线程运行结束"描述未出现,说明异步任务丢失了,我们都知道java线程依赖jvm实例,这里线程“死亡了”,单元测试执行完毕等多久才会销毁jvm呢,接下来打开钩子函数
确实看到钩子函数被立即执行了,并不会等子线程执行结束。
那jvm的销毁是否和守护线程、用户线程有关呢?答案是否定的,大家可以打开上面代码中“守护线程标记”做下实验,就不演示了。
接下来我们再试下main函数的情况
main函数模拟
public static void main(String[] args) {
//jvm关闭钩子函数
//Runtime.getRuntime().addShutdownHook(new Thread(() -> log.info("钩子开始执行:{}, {}", Thread.currentThread().getName(), Thread.currentThread().isDaemon())));
log.info("main线程号:{},守护线程:{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
Thread t = new Thread(()->{
log.info("sub线程号:{},守护线程:{}", Thread.currentThread().getName(), Thread.currentThread().isDaemon());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("子线程运行结束");
});
// 守护线程标记
t.setDaemon(false);
t.start();
log.info("main线程运行结束");
}
执行结果
看起来main函数执行完最后一行并没有影响子线程的执行,说明jvm实例销毁是在子线程执行完毕才进行的,我们打开钩子函数再次执行
jvm的销毁是发生在函数所有任务全部执行完毕才进行的。
另外,我们知道当程序只有守护线程时,也就是说守护线程没有了待守护的用户线程,jvm会被立即销毁,那是否真的是这样呢?我们将子线程的守护线程属性设置为true,结果确实如期,我们并没有看到"子线程运行结束",也就是说子线程随着jvm一起被销毁了
疑问
为什么spring单元测试会立即销毁jvm实例,而main函数不会,我想还是跟spring容器的消亡有关,单元测试执行完最后一行,spring容器会立即销毁,此时会销毁jvm实例,后续再研究下spring、spring bean的生命周期相关的知识点