经常使用线程,没有对守护线程和用户线程的区别做彻底了解
下面写4个例子来验证一下
源码如下
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;/*** Marks this thread as either a {@linkplain #isDaemon daemon} thread* or a user thread. The Java Virtual Machine exits when the only* threads running are all daemon threads.** <p> This method must be invoked before the thread is started.** @param on* if {@code true}, marks this thread as a daemon thread** @throws IllegalThreadStateException* if this thread is {@linkplain #isAlive alive}** @throws SecurityException* if {@link #checkAccess} determines that the current* thread cannot modify this thread*/
public final void setDaemon(boolean on) {checkAccess();if (isAlive()) {throw new IllegalThreadStateException();}daemon = on;
}
看注释的含义是,daemon 为 true 时是守护线程,false 为用户线程。当所有守护线程在执行时 jvm 会退出。
守护线程无循环逻辑
public class DaemonThreadTest {public static void main(String[] args) {Thread thread = new Thread(() -> {System.out.println("这是一个守护线程");});// 设置守护线程thread.setDaemon(true);thread.start();}
}
执行结果无内容,说明 main 在执行完后就结束了,子线程没来得及执行。
守护线程有循环逻辑
public class DaemonThreadCycleTest {public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {}});// 设置守护线程thread.setDaemon(true);thread.start();System.out.println("主线程测试守护线程结束");}
}
执行结果
主线程测试守护线程结束
同上
用户线程无循环逻辑
public class UserThreadTest {public static void main(String[] args) {Thread thread = new Thread(() -> {System.out.println("这是一个用户线程");});thread.start();}
}
执行结果
这是一个用户线程
主线程结束后,子线程执行了。
用户线程有循环逻辑
public class UserThreadCycleTest {public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {}});thread.start();System.out.println("主线程测试用户线程结束");}
}
执行结果
主线程测试用户线程结束
此时,idea 显示程序执行还未结束,查看 jps 信息如下
C:\Users\Rike>jps
27776
852 Jps
34568
29100 UserThreadCycleTest
29468 RemoteMavenServer
30684 Launcher
说明主线程结束了,但是用户线程还在执行。
结论
守护线程的一个实现,jvm的垃圾回收,需要时创建,不需要时销毁。
如果希望 main 线程在结束后子线程马上结束,在创建时显式设置为守护线程。
如果希望 main 线程在结束后子线程继续执行,在创建时设置为用户线程或者不指定(默认为用户线程)。
决定程序是否还执行的是用户线程,但是用户线程一直持续会有资源消耗的问题。因为jvm每个线程与cpu一一对应,线程存在会一直占用操作系统和机器资源。
考虑到执行任务的问题,例如读取或者写入文件的时候,使用用户线程为好,但是需要设置退出策略。
疑问
https://www.jianshu.com/p/91028dca187b
个人猜测,main 线程是用户线程,如果是守护线程不符合逻辑,因为很多线程建立在它基础上,需要做很多关闭操作。看到这篇文章也是这样讲,但是没讲证据。正确性需要后面进行验证。
考虑到了线程池 ThreadPoolExecutor 和异步执行器 CompletableFuture,看一下它们创建的线程是否有区别?
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
如果 ThreadPoolExecutor 创建时 ThreadFactory 不指定,默认是 Executors.defaultThreadFactory(),可以自己指定。
ThreadFactory 建议是自己指定,在实际项目执行时如果有多个线程池未进行指定会无法区别是哪个线程池在执行,出现问题也不好排查,指定了线程池名称,通过日志可以快速定位到问题。
Executors
可以看到,不管如何创建的线程一律是用户线程(既然强制这样设置,感觉这里的判断多余了,题外话),想到这里符合后台任务处理的情况,防止主程序退出了后台任务也结束了。
CompletableFuture
CompletableFuture 默认使用的线程池是 ForkJoinPool。
经过断点调试,最终 ForkJoinWorkerThreadFactory 的实现类是 DefaultForkJoinWorkerThreadFactory
通过 DefaultForkJoinWorkerThreadFactory 来创建线程
CompletableFuture 最终创建的线程是守护线程。
由此发现,ThreadPoolExecutor 和 CompletableFuture 创建的线程方式不一样。
如果 ThreadPoolExecutor 是计算型非io任务的话,可以自定义线程工厂来处理使用守护线程这个问题。
参考链接
https://mp.weixin.qq.com/s/j_dwm-foKDTghMxwmdsz2w
https://blog.csdn.net/MaYuKang/article/details/121931517
https://blog.csdn.net/weixin_34268604/article/details/114863702