Skip to content

面试速答(先看这里)

**一句话结论:**在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。

60秒标准回答:

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用户线程一般用于执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)

这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有<用户线程>都结束后就会退出,而不会等<守护线程>执行完

我们可以通过使用setDaemon()方法通过传递true作为参数,使线程成为一个守护线程。我们必须在启动线程之前调用一个线程的setDaemon()方法。否则,就会抛出一个java.lang.IllegalThreadStateException

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有<用户线程>都结束后就会退出,而不会等<守护线程>执行完。
  • **要点2:**我们可以通过使用setDaemon()方法通过传递true作为参数,使线程成为一个守护线程。
  • **要点3:**我们提到,当JVM中只剩下守护线程的时候,JVM就会退出,那么写一段代码测试下:
  • **要点4:**以上代码中,我们在Main线程中开启了一个子线程,在并没有显示将其设置为守护线程的情况下,他是一个用户线程,代码比较好理解,就是子线程处于一个while(true)循环中,每隔一秒打印一次I'm child thread..
  • **要点5:**值得注意的是,在Daemon线程中产生的新线程也是Daemon的。

**记忆锚点:**Daemon → 用户线程 → 守护线程 → true → JVM → setDaemon

易错提醒:

  • 值得注意的是,在Daemon线程中产生的新线程也是Daemon的。

加分表达:

  • 我们可以通过使用setDaemon()方法通过传递true作为参数,使线程成为一个守护线程。

追问准备:

  • 围绕「Daemon」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「用户线程」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「守护线程」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用户线程一般用于执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)。

这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有<用户线程>都结束后就会退出,而不会等<守护线程>执行完。

扩展知识 ​

创建守护线程 ​

我们可以通过使用setDaemon()方法通过传递true作为参数,使线程成为一个守护线程。我们必须在启动线程之前调用一个线程的setDaemon()方法。否则,就会抛出一个java.lang.IllegalThreadStateException。

可以使用isDaemon()方法来检查线程是否是守护线程。

plain
/**
* @author Hollis
*/
public class Main {
  public static void main(String[] args) {
    Thread t1 = new Thread();
    System.out.println(t1.isDaemon());
    t1.setDaemon(true);
    System.out.println(t1.isDaemon());
    t1.start();
    t1.setDaemon(false);
  }
}

以上代码输出结果:

plain
false
true
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at com.hollis.Main.main(Main.java:16)

我们提到,当JVM中只剩下守护线程的时候,JVM就会退出,那么写一段代码测试下:

plain
/**
* @author Hollis
*/
public class Main {
  public static void main(String[] args) {
    Thread childThread = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          System.out.println("I'm child thread..");
            try {
              TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
           	 e.printStackTrace();
            }
          }
        }
      });
    childThread.start();
    System.out.println("I'm main thread...");
  }
}

以上代码中,我们在Main线程中开启了一个子线程,在并没有显示将其设置为守护线程的情况下,他是一个用户线程,代码比较好理解,就是子线程处于一个while(true)循环中,每隔一秒打印一次I'm child thread..

输出结果为:

plain
I'm main thread...
I'm child thread..
I'm child thread..
.....
I'm child thread..
I'm child thread..

我们再把子线程设置成守护线程,重新运行以上代码。

plain
/**
* @author Hollis
*/
public class Main {
    public static void main(String[] args) {
    Thread childThread = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
        	System.out.println("I'm child thread..");
          try {
        	  TimeUnit.MILLISECONDS.sleep(1000);
          } catch (InterruptedException e) {
         	 e.printStackTrace();
          }
        }
      }
    });
    childThread.setDaemon(true);
    childThread.start();
    System.out.println("I'm main thread...");
  }
}

以上代码,我们通过childThread.setDaemon(true);把子线程设置成守护线程,然后运行,得到以下结果:

plain
I'm main thread...
I'm child thread..

子线程只打印了一次,也就是,在main线程执行结束后,由于子线程是一个守护线程,JVM就会直接退出了。

值得注意的是,在Daemon线程中产生的新线程也是Daemon的。