1.10 揭秘守护线程

在Java中,线程可以分为两类:

• 用户线程。

• 守护线程。

从使用方法来说,它们两个唯一的区别在于,守护线程在启动之前,需要通过setDaemon(true)方法进行设置,代码如下。

从功能层面来说,守护线程和用户线程最大的区别在于,守护线程不会影响JVM进程的退出,而用户线程在有任务没有执行完成前,JVM进程不会退出直到所有用户线程运行结束。

下面我们通过一个案例来分析,代码如下。

在DaemonThreadExample这个案例中,我们在main()方法中创建了一个thread线程,该线程通过一个while(true)循环不断运行,避免该线程执行结束(默认情况下主线程中创建的所有线程都是用户线程)。接着启动该线程让它执行,在main()方法最后打印一段话表示主线程结束。

这段代码的输出结果如下,可以发现即便main()方法运行结束,该JVM进程也是处于存活状态,而且用户线程一直处于运行中。也就是说在一个JVM进程中,只要有一个用户线程在运行,该JVM进程就无法正常退出。

于是我们对上述代码进行修改,把thread线程设置为守护线程,代码如下。

Runtime.getRuntime().addShutdownHook()可以用来注册JVM关闭的钩子,这个钩子可以在程序正常退出、系统关闭、OOM宕机时被回调。

上述程序运行结果如下,可以发现虽然thread线程没有运行完,但当main()方法运行结束后,该JVM进程仍然退出了。

1.10.1 守护线程的应用场景

可能读者会觉得守护线程看起来好像没什么用?但是实际上它的作用很大。

• 比如在JVM中垃圾回收器就采用了守护线程,如果一个程序中没有任何用户线程,那么就不会产生垃圾,垃圾回收器也就不需要工作了。

• 在一些中间件的心跳检测、事件监听等涉及定时异步执行的场景中也可以使用守护线程,因为这些都是在后台不断执行的任务,当进程退出时,这些任务也不需要存在,而守护线程可以自动结束自己的生命周期。

从这些实际场景中可以看出,对于一些后台任务,当不希望阻止JVM进程结束时,可以采用守护线程。

1.10.2 守护线程使用注意事项

平时我们用到的守护线程的场景不多,大部分场景都集成在各种中间件中。对业务开发来说一般会用线程池,而线程池默认都是非守护线程,并不需要我们专门去设置线程的守护类型,守护线程的使用需要注意以下两点。

• 在Java中,线程的状态是自动继承的。也就是说,如果一个线程是用户线程,那么它创建的子线程默认都是用户线程;如果一个线程是守护线程,那么它创建的子线程默认就是守护线程。

• thread.setDaemon(true)必须在start()方法启动之前调用,因为通常我们会将一个正在运行的线程设置为守护线程。