守护线程、非守护线程(用户线程)

参考:Java的守护线程与非守护线程

Thread.isDaemon(), 查看是否为守护线程。
守护线程,daemon 值为 true,比如 垃圾回收线程;
非守护线程,daemon 值为 false,比如 main 方法的线程;

所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

在创建线程时,如果不指定线程的 daemon 属性,线程会继承到创建该线程的线程 daemon 值,即原来线程daemon 是什么,新的线程就是什么。当然,新的线程可以手动指定 daemon 值(Thread.setDaemon(boolean_value))。

测试线程的 daemon 属性

  1. /**
  2. * 测试守护线程
  3. */
  4. @Test
  5. public void test_005() {
  6. Thread currentThread = Thread.currentThread();
  7. System.out.println(String.format("1 threadName:%s, Is daemon? %s", currentThread.getName(),
  8. currentThread.isDaemon()));
  9. Thread outerThread = new Thread(() -> {
  10. Thread outer = Thread.currentThread();
  11. System.out.println(String.format("1 threadName:%s, Is daemon? %s", outer.getName(),
  12. outer.isDaemon()));
  13. Thread innerThread = new Thread(() -> {
  14. Thread inner = Thread.currentThread();
  15. System.out.println(String.format("1 threadName:%s, Is daemon? %s", inner.getName(),
  16. inner.isDaemon()));
  17. }, "inner-thread");
  18. // 内部重新设置 daemon 值,不从创建它的线程来继承 daemon 属性
  19. innerThread.setDaemon(false);
  20. innerThread.start();
  21. }, "outer-thread");
  22. // 将外部设定为 true
  23. outerThread.setDaemon(true);
  24. outerThread.start();
  25. /*
  26. 1 threadName:main, Is daemon? false
  27. 1 threadName:outer-thread, Is daemon? true
  28. 1 threadName:inner-thread, Is daemon? false
  29. */
  30. }
  31. /**
  32. * 测试守护线程
  33. */
  34. @Test
  35. public void test_004() {
  36. Thread currentThread = Thread.currentThread();
  37. System.out.println(String.format("1 threadName:%s, Is daemon? %s", currentThread.getName(),
  38. currentThread.isDaemon()));
  39. Thread outerThread = new Thread(() -> {
  40. Thread outer = Thread.currentThread();
  41. System.out.println(String.format("1 threadName:%s, Is daemon? %s", outer.getName(),
  42. outer.isDaemon()));
  43. Thread innerThread = new Thread(() -> {
  44. Thread inner = Thread.currentThread();
  45. System.out.println(String.format("1 threadName:%s, Is daemon? %s", inner.getName(),
  46. inner.isDaemon()));
  47. }, "inner-thread");
  48. innerThread.start();
  49. }, "outer-thread");
  50. // 将外部设定为 true
  51. outerThread.setDaemon(true);
  52. outerThread.start();
  53. /*
  54. 1 threadName:main, Is daemon? false
  55. 1 threadName:outer-thread, Is daemon? true
  56. 1 threadName:inner-thread, Is daemon? true
  57. */
  58. }
  59. /**
  60. * 测试守护线程
  61. */
  62. @Test
  63. public void test_003() {
  64. Thread currentThread = Thread.currentThread();
  65. System.out.println(String.format("1 threadName:%s, Is daemon? %s", currentThread.getName(),
  66. currentThread.isDaemon()));
  67. Thread outerThread = new Thread(() -> {
  68. Thread outer = Thread.currentThread();
  69. System.out.println(String.format("1 threadName:%s, Is daemon? %s", outer.getName(),
  70. outer.isDaemon()));
  71. Thread innerThread = new Thread(() -> {
  72. Thread inner = Thread.currentThread();
  73. System.out.println(String.format("1 threadName:%s, Is daemon? %s", inner.getName(),
  74. inner.isDaemon()));
  75. }, "inner-thread");
  76. innerThread.start();
  77. }, "outer-thread");
  78. outerThread.start();
  79. /*
  80. 1 threadName:main, Is daemon? false
  81. 1 threadName:outer-thread, Is daemon? false
  82. 1 threadName:inner-thread, Is daemon? false
  83. */
  84. }

使用daemon 注意

将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点: (1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (2) 在Daemon线程中产生的新线程也是Daemon的。 (3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

小结

jvm 退出,先要等待非守护线程全部退出后,才会退出,可以看 Runtime.getRuntime().addShutdownHook 方法

java.lang.Runtime public void addShutdownHook(@NotNull Thread hook) 注册一个新的虚拟机关闭钩子。 Java 虚拟机关闭以响应两种事件: 程序正常退出,当最后一个非守护线程退出或调用exit (相当于System.exit )方法时,或 虚拟机响应用户中断(例如键入^C )或系统范围的事件(例如用户注销或系统关闭)而终止。 关闭钩子只是一个初始化但未启动的线程。 当虚拟机开始其关闭序列时,它将以某种未指定的顺序启动所有已注册的关闭挂钩,并让它们同时运行。 当所有钩子都完成后,如果启用了退出时终结,它将运行所有未调用的终结器。 最后,虚拟机将停止。 请注意,守护线程将在关闭序列期间继续运行,如果关闭是通过调用exit方法启动的,则非守护线程也将继续运行。 一旦关闭序列开始,它只能通过调用halt方法来停止,该方法强行终止虚拟机。 一旦关闭序列开始,就不可能注册新的关闭挂钩或取消注册先前注册的挂钩。 尝试这些操作中的任何一个都会导致IllegalStateException 。 关闭钩子在虚拟机生命周期的一个微妙时刻运行,因此应该进行防御性编码。 特别是,它们应该被编写为线程安全的,并尽可能避免死锁。 他们也不应该盲目依赖可能已经注册了自己的关闭钩子的服务,因此他们自己可能会在关闭过程中。 例如,尝试使用其他基于线程的服务(例如 AWT 事件分派线程)可能会导致死锁。 关闭钩子也应该快速完成它们的工作。 当程序调用exit ,期望虚拟机将立即关闭并退出。 当虚拟机由于用户注销或系统关闭而终止时,底层操作系统可能只允许关闭和退出的固定时间量。 因此,不建议尝试任何用户交互或在关闭挂钩中执行长时间运行的计算。 通过调用线程的ThreadGroup对象的uncaughtException方法,在关闭钩子中处理未捕获的异常就像在任何其他线程中一样。 此方法的默认实现将异常的堆栈跟踪打印到System.err并终止线程; 它不会导致虚拟机退出或停止。 在极少数情况下,虚拟机可能会中止,即在没有彻底关闭的情况下停止运行。 当虚拟机从外部终止时会发生这种情况,例如在 Unix 上使用SIGKILL信号或在 Microsoft Windows 上使用TerminateProcess调用。 如果本地方法出错,例如破坏内部数据结构或尝试访问不存在的内存,虚拟机也可能中止。 如果虚拟机中止,则无法保证是否会运行任何关闭挂钩。

参数: hook - 一个初始化但未启动的Thread对象 抛出: IllegalArgumentException – 如果指定的钩子已经被注册,或者可以确定钩子已经在运行或已经运行 IllegalStateException – 如果虚拟机已在关闭过程中 SecurityException – 如果存在安全管理器并且拒绝RuntimePermission (“shutdownHooks”) 自从: 1.3 也可以看看: removeShutdownHook , halt(int) , exit(int) 推断注释: @org.jetbrains.annotations.NotNull < 1.8 >

Thread 类上说明

线程是程序中的执行线程。 Java 虚拟机允许应用程序同时运行多个执行线程。 每个线程都有一个优先级。 具有较高优先级的线程优先于具有较低优先级的线程执行。 每个线程可能也可能不标记为守护进程。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时,新线程才是守护线程。 当 Java 虚拟机启动时,通常有一个非守护线程(通常调用某个指定类的名为main的方法)。 Java 虚拟机继续执行线程,直到发生以下任一情况: 已调用Runtime类的exit方法并且安全管理器已允许退出操作发生。 不是守护线程的所有线程都已死亡,无论是从对run方法的调用返回,还是通过抛出传播到run方法之外的异常。

jvm 的退出,需要非守护线程结束。
非守护线程结束后,守护线程也会被结束,jvm 退出(至于 jvm 退出,需不需要等待 daemon thread 结束,目前尚不清楚,表现出来的现象是 jvm 不会等待守护线程结束,就会退出,守护线程随着 jvm 的结束而结束)。

个人觉得守护线程的作用就是保护(管理非守护线程),所以守护线程必须在所有非守护线程终止后才能退出。

Timer 类

timer 构造器,就已经将 thread 启动。
属性 TaskQueue,存放任务 TimerTask。TimerTask 是一个 Runnable 接口的实现类。
属性 TimerThread,线程执行 TaskQueue 中的任务。TimerThread,是 Thread 的一个子类。
ThreadPoolExecutor.png
代码测试

  1. public static void main(String[] args) {
  2. System.out.println("当前时间:" + new Date());
  3. Calendar calendar = Calendar.getInstance();
  4. calendar.add(Calendar.SECOND, 10);
  5. Date date = calendar.getTime();
  6. // 线程是否为 daemon
  7. Timer timer = new Timer(false);
  8. final List<Integer> list = new ArrayList<>();
  9. timer.schedule(new TimerTask() {
  10. @Override
  11. public void run() {
  12. System.out.println("i value is " + list.size());
  13. }
  14. }, date);
  15. System.out.println(">>>>>>>>>>>>>>");
  16. }

当 Timer 设置为 Daemon,创建的 Thread 就是 Daemon,Timer 可能就会在退出,不能够做到一直执行任务;
当 Timer 不是 Daemon 时, timer 的 thread 会一直执行(while(true) 线程一直循环,不退出)。
虽然 Daemon 时,也是 while(true),但是在没有非守护线程后,jvm 会退出。那么是不是只要 jvm 不退出,我们也可以使用 Daemon 的timer 呢?守护线程跟非守护线程,都会执行,所以根据实际情况设置吧。

疑问

多个