一、前言
借鉴文章:
文章1
文章2
①Java提供的两种线程
Java提供了两种线程: 守护线程和用户线程(非守护线程)
守护线程(Daemon Thread): 在程序运行时 在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。任何一个守护线程都是整个JVM中所有非守护线程的“保姆”。
-
守护线程是一种在后台提供服务的线程,它的存在并不会阻止Java虚拟机(JVM)的退出。
-
当所有的非守护线程执行完毕或者终止时,JVM会检查是否还有任何活动的守护线程。如果只剩下守护线程,JVM会优雅地关闭守护线程并退出。
-
可以通过调用Thread类的setDaemon(true)方法将线程设置为守护线程。
用户线程(非守护线程,Non-daemon Thread): 用户线程基本上和守护线程一样,唯一的不同之处在于如果用户线程全部退出运行,只剩下守护线程存在了,JVM就会退出。因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有工作可做,当然也就没有继续执行的必要了,程序就会终止,同时会杀死所有的"守护线程",也就是说只要有任何非守护线程还在运行,程序就不会终止
- 非守护线程是一种正常的线程,它的存在会阻止JVM的退出,直到所有的非守护线程执行完毕或者手动调用System.exit()方法退出程序。
- 默认情况下,通过Thread类创建的线程都是非守护线程。
②JVM正常情况的退出
JDK官方文档:
The Java Virtual Machine exits when the only threads running are all daemon threads.
当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。
③守护线程应用场景(作用)
当JVM中没有一个正在运行的非守护线程,这个时候JVM就会退出,也就是说守护线程拥有自动结束自己声明周期的特性,而非守护线程不具备这个特点JVM中的垃圾回收线程就是典型的守护进程,当非守护线程都没有了,那要垃圾回收线程干什么,直接结束JVM即可。守护线程一般用于执行一些后台任务,在程序或JVM退出时候,守护线程能够自动关闭,具体场景如下:
- 后台任务处理:守护线程通常用于执行一些后台任务,这些任务对于整个程序的运行并不是必需的,而且在程序的主要逻辑结束时可以被安全地终止。例如,日志记录、性能统计、定时任务等可以作为守护线程来执行。
- 服务监控和管理:守护线程常常被用来监控和管理其他线程或资源,确保它们的正常运行。例如,在服务器应用中,可以使用守护线程来监控网络连接、数据库连接等资源,及时释放无用资源或重新尝试连接。
- 辅助性工作:一些辅助性的工作可以交给守护线程来完成,比如周期性的数据清理、定时任务的执行等。
- 资源回收:守护线程通常用于执行垃圾回收(Garbage Collection)工作,确保程序的内存得到及时的回收和释放,以避免内存泄漏问题。
④Hook线程(下面会用到)
Hook线程概念:
Hook线程即钩子(Hook)线程,在程序即将退出,即JVM程序即将退出的时候,Hook线程会被启动执行。
Hook线程代码示例:
package com;
import java.util.concurrent.TimeUnit;
/**
* @author banana
* @create 2023-11-07 16:27
*/
public class HookTask {
public static void main(String[] args) {
//为应用程序注入Hook(钩子)线程
Runtime.getRuntime().addShutdownHook(new Thread(() ->{
System.out.println("the hook thread 1 is running.");
try {
//休眠2秒
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("the hook thread 1 will exit");
}));
System.out.println("the main thread will exit.");
}
}
运行结果:
可以看出,当主线程结束的,也就是JVM进程即将退出的时候,注入的Hook线程启动并打印相关日志。
注意事项:
- Hook 线程只有在正确接收到退出信号时,才能被正确执行,如果你是通过
kill -9
这种方式,强制杀死的进程,那么抱歉,进程是不会去执行 Hook 线程的,为什么呢?你想啊,它自己都被强制干掉了,哪里还管的上别人呢? - 请不要在 Hook 线程中执行一些耗时的操作,这样会导致程序长时间不能退出。
二、守护线程实例
①非守护线程不退出情况
代码:
public class SHOUHUXCTS {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
//睡眠一会
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
});
//启动用户线程(这里是非守护线程)
thread.start();
//主线程退出
System.out.println("The main thread ready to exit…");
}
}
运行结果:
可以看到,主线程main thread
已经结束了,但是我们自定义的线程(非守护线程)是会持续运行的,即JVM
进程依赖没有退出,当前非守护线程仍旧在运行中。
②守护线程退出情况
代码:
package com.yjy;
import java.util.concurrent.TimeUnit;
/**
* @author banana
* @create 2023-11-07 15:44
*/
public class SHOUHUXCTS {
public static void main(String[] args) {
//设置一个钩子线程,在JVM退出时输出日志
Runtime.getRuntime()
.addShutdownHook(new Thread(() -> System.out.println("The JVM exit success")));
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
//睡眠一会
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
});
//设置当前线程为守护线程
thread.setDaemon(true);
//启动用户线程(这里是非守护线程)
thread.start();
//主线程退出
System.out.println("The main thread ready to exit…");
}
}
运行结果:
可以看到当主线程退出时,JVM会随之退出运行,守护线程也同时会被回收。