java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。
- 用户线程:我们平时所创建的都是用户线程。
- 守护线程:为用户线程提供服务。
任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(boolean);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在 Thread.start()之前调用,否则运行时会抛出异常。
两者的区别:
唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;比如 JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。
写个程序来验证一下:
package com.top.test.mutiTheread;
public class DaemonTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
System.out.println("main线程是守护线程:" + Thread.currentThread().isDaemon());
System.out.println("------------------------------------------");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
System.out.println(Thread.currentThread().getName() + "我是守护线程t1");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.setName("守护线程t1");
t1.setDaemon(true);
Thread t2 = new Thread(() -> {
try {
int i = 1;
while (i <= 10) {
System.out.println(Thread.currentThread().getName() + ":我是用户线程t2---" + i);
Thread.sleep(500);
i++;
}
System.out.println(Thread.currentThread().getName() + ":我是用户线程t2---我执行完run方法了");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t2.setName("用户线程t2");
t1.start();
t2.start();
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("-------------");
System.out.println("Java虚拟机退出啦!");
}
}));
}
}
执行结果:
main
main线程是守护线程:false
------------------------------------------
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---1
用户线程t2:我是用户线程t2---2
守护线程t1我是守护线程t1
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---3
用户线程t2:我是用户线程t2---4
守护线程t1我是守护线程t1
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---5
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---6
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---7
用户线程t2:我是用户线程t2---8
守护线程t1我是守护线程t1
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---9
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---10
守护线程t1我是守护线程t1
用户线程t2:我是用户线程t2---我执行完run方法了
-------------
Java虚拟机退出啦!
从执行结果可以看出:
- Main线程不是守护线程;
- 用户线程退出后,守护线程退出,随即Java虚拟机退出;
对于Runtime.getRuntime().addShutdownHook()这个方法,我解释一下:这个方法的意思就是在jvm中增加一个关闭的钩子hook(一个线程对象),当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。
可以增加多个钩子,我们可以看下其源码:
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
// 最终添加到hooks对象中,hooks是一个map对象,可以存储多个钩子。
hooks.put(hook, hook);
}
// hooks 的定义
private static IdentityHashMap<Thread, Thread> hooks;
多线程连载:
Java内存模型-volatile的应用(实例讲解)
synchronized的三种应用方式(实例讲解)
可重入锁-synchronized是可重入锁吗?
大彻大悟synchronized原理,锁的升级
一文弄懂Java的线程池
公平锁和非公平锁-ReentrantLock是如何实现公平、非公平的
一图全面了解Java线程的生命周期