JUnit测试中ExecutorService无法执行Runnable任务的问题排查
这种测试场景下任务跑不起来的情况我碰过好几次,给你分几个方向排查:
先确认任务真的被提交到线程池了
别笑,有时候测试代码里可能因为分支判断、mock注入的问题,executor.submit(runnable)这句根本没执行到。你可以在提交任务前后加个日志或者打印,比如:System.out.println("Before submit: queue size = " + executor.getQueue().size()); executor.submit(myRunnable); System.out.println("After submit: queue size = " + executor.getQueue().size());如果队列大小没变化,那问题就出在任务提交环节,先把这个搞定。
检查测试框架的线程生命周期
比如用JUnit、TestNG这类框架时,测试方法执行完后,主线程会直接结束,JVM可能会终止所有非守护线程(或者如果线程池是守护线程的话直接干掉),导致ExecutorService的任务还没来得及执行就被终止了。
解决方法很简单:在测试代码最后加上等待逻辑,比如:executor.shutdown(); boolean terminated = executor.awaitTermination(15, TimeUnit.SECONDS); if (!terminated) { executor.shutdownNow(); }这样能保证测试主线程等待线程池里的任务执行完毕再结束。
排查线程池的线程配置
如果你自定义了线程工厂,看看是不是把线程设成了守护线程。守护线程会在JVM只剩守护线程时自动终止,测试主线程结束后,线程池的守护线程就没机会跑任务了。检查线程工厂的代码,比如有没有thread.setDaemon(true)这种配置,有的话改成非守护线程试试。排除异常被静默吞噬的情况
有些测试框架或者mock工具会篡改线程的未捕获异常处理逻辑,导致Runnable的run()方法里如果抛出异常,直接被吃掉了,看起来像是没执行。你可以在run()方法最开头加个日志输出,比如System.out.println("Task " + this + " started running"),如果有输出说明任务已经启动,只是后面出了问题;如果没输出,那就是任务根本没被调度。简化测试代码,逐步定位问题
先把wait/notify的逻辑去掉,让Runnable只做简单的打印操作,看能不能正常执行。如果能执行,再逐步加回wait/notify的逻辑,每次加一部分就跑测试,看在哪一步出问题。比如先加锁和wait(),再加定时器的notify(),这样能快速定位是锁的问题还是定时器的问题。检查线程池的状态
会不会在提交任务之前,线程池已经被调用了shutdown()或者shutdownNow()?这种情况下线程池不会再接受新任务,提交的任务会被拒绝(默认是抛出RejectedExecutionException,但如果用了自定义拒绝策略,可能会静默丢弃)。可以在提交任务前打印executor.isShutdown(),如果返回true,那就是线程池已经关闭了。
内容的提问来源于stack exchange,提问作者Олег Місько

