今天在 Junit 中尝试调试多线程业务,结果发现,testXX 方法中早早就退出了虚拟机,启动的子线程全都压根没有执行,也就是 Junit 中无法正常的测试多线程。
一开始,我以为是在主线程中出现了异常,导致了虚拟机的退出,但是做了一个小实验分析了一下,发现不是那么回事。经过试验,我发现,无论是 Exception
RuntimeException
甚至 Error
都不能造成虚拟机的退出,子线程都能被执行到,只有 System.exit(n)
才能直接退掉虚拟机,忽略子线程的执行。
package com.github.since1986.learn.java; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class LearnThread { public static void main(String[] args) throws Exception { int subThreadCount = 9; ExecutorService executorService = Executors.newFixedThreadPool(subThreadCount); for (int i = 0; i < subThreadCount; i++) { executorService.submit(() -> { System.out.printf("线程 [%s] 运行/n", Thread.currentThread().getName()); }); } //throw new Exception(); //throw new RuntimeException(); //throw new Error(); System.exit(0); } }
输出
Process finished with exit code 0
因此,大致可以猜测,Junit 中在什么地方调用了 System.exit()
所以造成了子线程无法执行,Google 了一下, 有这篇文章 也研究了这个问题,果然在 Junit 中有一个类 org.junit.runner.JUnitCore
包含了 System.exit()
/** * Run the tests contained in the classes named in the <code>args</code>. * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1. * Write feedback while tests are running and write * stack traces for all failed tests after the tests all complete. * * @param args names of classes in which to find tests to run */ public static void main(String... args) { Result result = new JUnitCore().runMain(new RealSystem(), args); System.exit(result.wasSuccessful() ? 0 : 1); }
我想在这个类上打上断点试一下,走一遍,结果惊奇的发现,断点并没有走到这个,然后看了看 debug 窗中线程 main
发现有这么一个类 com.intellij.rt.execution.junit.JUnitStarter
最后 main()
原来是在这了类里的,这个类明显是 idea 内部的一个插件类。
Google 了一下,实际上这个插件是开源的, 可以看到这个类的源代码 ,大致上看了一眼,里面也是调用了 System.exit()
(不管怎样,最后 testXX 方法肯定会是通过 main()
来执行的,所以我们看 main()
就好了),所以,大只能印证个大概,是因为 Junit 当中使用了 System.exit()
,所以导致了子线程的执行因为虚拟机的退出而被忽略,所以无法正常测试多线程。
public static void main(String[] args) throws IOException { Vector argList = new Vector(); for (int i = 0; i < args.length; i++) { String arg = args[i]; argList.addElement(arg); } final ArrayList listeners = new ArrayList(); final String[] name = new String[1]; String agentName = processParameters(argList, listeners, name); if (!JUNIT5_RUNNER_NAME.equals(agentName) && !canWorkWithJUnitVersion(System.err, agentName)) { System.exit(-3); } if (!checkVersion(args, System.err)) { System.exit(-3); } String[] array = new String[argList.size()]; argList.copyInto(array); int exitCode = prepareStreamsAndStart(array, agentName, listeners, name[0]); System.exit(exitCode); }
其实明白了大致的原因,解决起来也就简单了,让主线程等待子线程都执行完毕就好了,实现这一点有多种方式,可以用”倒计时门栓”或者”可循环使用屏障”。(实际上我中间请教了我的同事,他并没有分析这莫多,大致凭经验告诉我可以使用倒计时门栓,我当时试了确实管用)
通过这次解决工作中出现的一个问题的契机,我对 Junit 以及多线程又加深了一些理解。总的来说,对于理论的理解要在实践中不断的磨练才能越来越深入。