JUnit测试子线程未执行finally与Java main方法的差异原因
问题场景
示例1:JUnit测试方法无法打印finally块输出
如下为一个junit测试方法,执行该方法不会打印finally块的输出内容。
@Test public void junit_Thread() { new Thread(new Runnable() { @Override public void run() { System.out.println("run"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("finally"); } } }).start(); System.out.println("end"); }
示例2:普通main方法可正常打印finally块输出
如下为一个典型的Java main方法,执行该方法会等待3秒后打印finally块的输出内容。
public class MainThread { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("run"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("finally"); } } }).start(); System.out.println("main thread exit"); } }
问题
为何示例1的JUnit测试方法无法像示例2的main方法一样,等待子线程执行完成并输出finally块的内容,二者的JVM退出判定、线程生命周期管理逻辑存在什么差异?
原因解答
首先明确JVM的两类退出规则:
- 自然退出:进程内所有非守护(用户)线程全部执行完毕,JVM自动关闭
- 强制退出:代码主动调用
System.exit()方法,JVM会立刻终止所有运行中的线程、直接关闭进程,和当前存活线程的类型、执行状态无关。
普通main方法的运行逻辑
直接执行main方法启动Java程序时:
- main主线程默认是非守护线程,手动
new Thread()创建的子线程默认也继承非守护属性,不属于会被JVM自动回收的低优先级守护线程 - 当main方法执行到最后一行打印
main thread exit后,main线程运行结束,但此时启动的子线程还处于3秒sleep的存活状态,属于未完成的非守护线程 - JVM会保持运行状态,直到子线程sleep结束、执行完finally块的
finally打印、线程正常销毁后,才会自然退出,因此能看到预期的输出。
JUnit测试方法的运行逻辑
执行@Test标注的测试方法时,执行逻辑和直接跑main方法有本质区别:
- JUnit有独立的测试运行器,会在自己的调度线程中调用测试方法,当测试方法内的所有代码执行完毕(也就是打印完
end返回时),JUnit就判定当前测试用例执行完成 - JUnit不会主动等待测试方法中手动启动的子线程运行结束,测试方法返回后,JUnit的收尾流程会主动调用
System.exit(0)强制终止JVM,避免测试进程挂住 - 此时子线程的3秒等待还没结束,直接被JVM强制终止,没有机会执行到finally块的打印逻辑,自然看不到对应输出。
补充说明:不存在JUnit自动把子线程设为守护线程的情况,手动创建的子线程默认依然是非守护状态,只是System.exit()的强制终止逻辑不会给线程留执行收尾代码的时间。
内容的提问来源于stack exchange,提问作者tangcheng
相关产品推荐
相关产品推荐

