ExecutorService仅在Main方法正常运行,Test方法中提前终止如何解决
问题产生原因
这个问题本质是测试框架的运行生命周期和普通main方法启动的JVM生命周期规则不一致:
- 普通
main方法运行时,JVM退出的判定标准是所有非守护线程全部执行完毕。Executors.newScheduledThreadPool(1)创建的线程池默认生成非守护工作线程,所以哪怕main方法本身的代码跑完了,只要线程池的工作线程还活着,JVM就不会退出,定时任务能一直按固定频率跑,直到手动关闭线程池。 - 带
@Test注解的测试方法完全不遵循这个规则。不管是JUnit4还是JUnit5,只要测试方法所在的主线程把方法内的代码全部执行完、方法返回,测试框架就会直接终止整个测试进程,不会等待提交到线程池的后台异步任务继续运行。
你写的runner()方法里,scheduleAtFixedRate是异步提交任务的操作,调用后会立刻返回,不会阻塞当前线程。测试方法里调用完runner()后马上就执行到了方法末尾,框架直接终止进程,自然会出现ExecutorService提前终止、定时任务无法持续运行的现象。
解决方法
核心逻辑是不要让测试主线程在你需要的异步任务运行周期结束前提前执行完毕,常用方案有两种:
- 直接阻塞测试主线程,等够需要观测任务运行的时长后再退出,退出前记得手动关闭线程池释放资源,示例测试代码:
@Test public void testScheduledTask() throws InterruptedException { TestConsumer consumer = new TestConsumer(); consumer.runner(); // 主线程阻塞5秒,期间定时任务会正常按每秒一次的频率执行 Thread.sleep(5000); // 测试收尾阶段手动关闭线程池 consumer.executorService.shutdown(); }
- 借助线程池自带的
awaitTermination方法做阻塞等待,指定任务需要运行的时长,超时后再关闭线程池结束测试,示例代码:
@Test public void testScheduledTask() throws InterruptedException { TestConsumer consumer = new TestConsumer(); consumer.runner(); // 阻塞等待10秒,期间定时任务正常运行 consumer.executorService.awaitTermination(10, TimeUnit.SECONDS); // 等待超时后强制关闭线程池 consumer.executorService.shutdownNow(); }
如果需要做更精确的逻辑校验,比如验证任务确实执行了指定次数、输出结果符合预期,可以用CountDownLatch做计数,等任务执行到目标次数后计数归零,再放行主线程结束测试即可。
内容的提问来源于stack exchange,提问作者tabiodun
相关产品推荐
相关产品推荐

