核心概念

守护线程(Daemon Thread)是一种特殊的服务线程,为其他线程提供支持服务。当JVM中只剩下守护线程时,JVM会自动退出。

Thread thread = new Thread(() -> {
    // 线程逻辑
});
thread.setDaemon(true); // 设置为守护线程
thread.start();

关键特点

  • 守护线程是”后台服务”,不阻止JVM退出
  • 用户线程是”主要任务”,会阻止JVM退出
  • JVM退出时,守护线程会立即终止(不会执行finally块)

守护线程 vs 用户线程

对比表

特性 用户线程(User Thread) 守护线程(Daemon Thread)
默认类型 否(需显式设置)
JVM退出 阻止退出 不阻止退出
创建方式 new Thread() setDaemon(true)
父线程影响 继承父线程类型 继承父线程类型
典型用途 业务逻辑 后台服务(GC、监控)
finally执行 ✅ 保证执行 ❌ 可能不执行

示例对比

用户线程(阻止JVM退出)

public class UserThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("用户线程执行:" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        thread.start(); // 默认为用户线程
        System.out.println("main线程结束");
    }
}

输出

main线程结束
用户线程执行:1
用户线程执行:2
用户线程执行:3
用户线程执行:4
用户线程执行:5
// 5秒后JVM才退出

结论:main结束后,JVM等待用户线程执行完毕。

守护线程(不阻止JVM退出)

public class DaemonThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("守护线程执行:" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        thread.setDaemon(true); // 设置为守护线程
        thread.start();
        System.out.println("main线程结束");
    }
}

输出

main线程结束
守护线程执行:1
// JVM立即退出,守护线程被强制终止

结论:main结束后,JVM立即退出,守护线程被中断。

原理分析

1. JVM退出机制

// JVM退出判断逻辑(伪代码)
boolean shouldExit() {
    List<Thread> allThreads = getAllThreads();
    
    // 过滤出所有非守护线程
    List<Thread> userThreads = allThreads.stream()
        .filter(t -> !t.isDaemon())
        .collect(Collectors.toList());
    
    // 只剩守护线程时退出
    return userThreads.isEmpty();
}

2. 源码分析

设置守护线程

public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {
        // 必须在start()前调用
        throw new IllegalThreadStateException();
    }
    daemon = on; // 设置守护标志
}

关键点

  • 必须在start()之前调用
  • 启动后无法修改

线程类型继承

// Thread.init()方法片段
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // ...
    Thread parent = currentThread();
    
    // 继承父线程的守护属性
    this.daemon = parent.isDaemon();
    
    // ...
}

规则:子线程继承父线程的守护属性。

3. 常见守护线程

public class JVMDaemonThreads {
    public static void main(String[] args) {
        // 获取所有线程
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        while (rootGroup.getParent() != null) {
            rootGroup = rootGroup.getParent();
        }
        
        Thread[] threads = new Thread[rootGroup.activeCount()];
        rootGroup.enumerate(threads);
        
        for (Thread thread : threads) {
            if (thread != null) {
                System.out.printf("%s - Daemon: %b%n", 
                    thread.getName(), thread.isDaemon());
            }
        }
    }
}

典型输出

main - Daemon: false                   // 用户线程
Reference Handler - Daemon: true       // GC引用处理
Finalizer - Daemon: true               // finalize()方法执行
Signal Dispatcher - Daemon: true       // 信号分发
Attach Listener - Daemon: true         // 附加监听器

应用场景

1. 后台监控服务

public class MonitorService {
    private static final Thread monitorThread = new Thread(() -> {
        while (true) {
            try {
                // 监控系统状态
                System.out.println("心跳检测:" + LocalDateTime.now());
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                break;
            }
        }
    });

    static {
        monitorThread.setDaemon(true); // 设置为守护线程
        monitorThread.setName("Monitor-Thread");
        monitorThread.start();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主程序开始");
        Thread.sleep(12000); // 模拟业务执行
        System.out.println("主程序结束");
        // JVM退出,监控线程自动结束
    }
}

2. 日志异步刷盘

public class AsyncLogger {
    private final BlockingQueue<String> logQueue = new LinkedBlockingQueue<>();
    
    public AsyncLogger() {
        Thread flushThread = new Thread(() -> {
            while (true) {
                try {
                    String log = logQueue.take();
                    writeToFile(log);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        
        flushThread.setDaemon(true); // 守护线程
        flushThread.setName("Log-Flush-Thread");
        flushThread.start();
    }
    
    public void log(String message) {
        logQueue.offer(message);
    }
    
    private void writeToFile(String log) {
        // 写入文件逻辑
    }
}

注意:守护线程可能导致日志丢失,生产环境需优雅关闭。

3. 内存清理任务

public class MemoryCleaner {
    private static final ScheduledExecutorService scheduler = 
        Executors.newScheduledThreadPool(1, runnable -> {
            Thread thread = new Thread(runnable);
            thread.setDaemon(true); // 守护线程
            thread.setName("Memory-Cleaner");
            return thread;
        });

    static {
        scheduler.scheduleAtFixedRate(() -> {
            System.gc(); // 建议GC
            System.out.println("执行内存清理");
        }, 0, 10, TimeUnit.SECONDS);
    }
}

使用陷阱与注意事项

1. finally块可能不执行

public class DaemonFinallyDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("守护线程开始");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // ❌ 可能不会执行
                System.out.println("finally块执行");
            }
        });
        
        thread.setDaemon(true);
        thread.start();
        
        System.out.println("main结束");
        // JVM立即退出,finally可能不执行
    }
}

输出

main结束
守护线程开始
// finally块未执行!

2. 不适合I/O操作

// ❌ 危险:守护线程中执行I/O
Thread ioThread = new Thread(() -> {
    try (FileWriter writer = new FileWriter("data.txt")) {
        writer.write("重要数据");
        writer.flush(); // 可能JVM退出时未完成
    } catch (IOException e) {
        e.printStackTrace();
    }
});
ioThread.setDaemon(true); // 可能导致数据丢失
ioThread.start();

解决方案:使用用户线程 + 优雅关闭。

// ✅ 正确:用户线程 + shutdown钩子
Thread ioThread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // I/O操作
    }
});

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    ioThread.interrupt();
    try {
        ioThread.join(5000); // 等待最多5秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}));

ioThread.start();

3. 必须在start()前设置

Thread thread = new Thread(() -> {});
thread.start();
thread.setDaemon(true); // ❌ IllegalThreadStateException

4. 线程池中的守护线程

// 创建守护线程池
ThreadFactory daemonFactory = runnable -> {
    Thread thread = new Thread(runnable);
    thread.setDaemon(true); // 池中所有线程都是守护线程
    return thread;
};

ExecutorService executor = Executors.newFixedThreadPool(10, daemonFactory);

最佳实践

1. 明确线程类型

// ✅ 明确命名和类型
Thread thread = new Thread(() -> {
    // 任务逻辑
});
thread.setName("Business-Worker"); // 清晰的名称
thread.setDaemon(false);            // 明确指定类型
thread.start();

2. 优雅关闭守护线程

public class GracefulDaemon {
    private volatile boolean running = true;
    private final Thread daemonThread;

    public GracefulDaemon() {
        daemonThread = new Thread(() -> {
            while (running) {
                try {
                    // 执行任务
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
            // 清理资源
            cleanup();
        });
        
        daemonThread.setDaemon(true);
        daemonThread.start();
    }

    public void shutdown() {
        running = false;
        daemonThread.interrupt();
    }

    private void cleanup() {
        System.out.println("清理资源");
    }
}

3. 使用shutdown钩子

public class ShutdownHookDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                // 守护任务
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();

        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("JVM即将退出,执行清理");
            daemonThread.interrupt();
            try {
                daemonThread.join(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));
    }
}

答题总结

面试标准答案

守护线程(Daemon Thread)是一种后台服务线程,与用户线程的核心区别:

定义

  • 用户线程:主要任务线程,阻止JVM退出
  • 守护线程:后台服务线程,不阻止JVM退出

关键特性

  1. JVM退出机制:当所有用户线程结束时,JVM立即退出,守护线程被强制终止
  2. 设置时机:必须在start()之前调用setDaemon(true)
  3. 类型继承:子线程继承父线程的守护属性
  4. finally不保证执行:JVM退出时守护线程可能被中断,finally块可能不执行

典型应用

  • GC线程:垃圾回收
  • 监控线程:系统心跳、性能监控
  • 定时任务:缓存清理、日志刷盘

使用注意

  • ❌ 不适合重要I/O操作(可能丢失数据)
  • ❌ 不适合关键业务逻辑
  • ✅ 适合可中断的后台任务
  • ✅ 配合shutdown钩子优雅关闭

核心记忆:守护线程 = “配角”,用户线程 = “主角”,主角退场,配角立即下台。