← Назад к вопросам
Что такое потоки-демоны (daemon threads) в Java?
2.0 Middle🔥 131 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Потоки-демоны (Daemon Threads) в Java
Потоки-демоны — это специальный тип потоков, которые работают в фоне и не препятствуют завершению программы. Если остаются только демоны, JVM выключится, не дожидаясь их завершения.
Основное правило
JVM выходит, когда все не-демон потоки завершены, независимо от демонов.
Демон vs Обычный поток
| Характеристика | Обычный поток | Демон |
|---|---|---|
| Влияет на завершение JVM | ✓ Да | ✗ Нет |
| Может работать в фоне | Да, но JVM ждёт | ✓ Да, JVM не ждёт |
| Типичное использование | Основная логика | Фоновые задачи |
| Примеры | main, обработка запросов | Garbage Collector, таймеры |
| Установка | По умолчанию обычные | thread.setDaemon(true) |
Как создать демон-поток
public class DaemonThreadExample {
public static void main(String[] args) throws InterruptedException {
// Обычный поток
Thread normalThread = new Thread(() -> {
System.out.println("Обычный поток: начало работы");
try {
Thread.sleep(5000);
System.out.println("Обычный поток: завершение");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
normalThread.setName("Normal-Thread");
normalThread.start();
// Демон-поток
Thread daemonThread = new Thread(() -> {
System.out.println("Демон: начало работы");
while (true) {
try {
System.out.println("Демон: выполняет фоновую задачу...");
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Демон: прерван");
break;
}
}
System.out.println("Демон: завершение");
});
daemonThread.setName("Daemon-Thread");
daemonThread.setDaemon(true); // ✓ Важно! Сделай демоном ПЕРЕД start()
daemonThread.start();
System.out.println("Главный поток: рабочий код");
Thread.sleep(3000);
System.out.println("Главный поток: завершение");
}
}
// Вывод (примерный):
// Главный поток: рабочий код
// Обычный поток: начало работы
// Демон: начало работы
// Демон: выполняет фоновую задачу...
// Демон: выполняет фоновую задачу...
// Главный поток: завершение
// Обычный поток: завершение
// [JVM выходит, демон не завершается]
Критическое правило: setDaemon() перед start()
Thread thread = new Thread(() -> {});
// ✓ Правильно
thread.setDaemon(true);
thread.start();
// ✗ НЕПРАВИЛЬНО - выбросит исключение
thread.start();
thread.setDaemon(true); // java.lang.IllegalThreadStateException!
Примеры демон-потоков в реальной жизни
1. Garbage Collector (встроенный демон)
public class GCDaemonExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("Главный поток выполняется");
Thread.sleep(3000);
System.out.println("Главный поток завершается");
// GC (демон) продолжает работать, но JVM выходит,
// потому что GC не блокирует завершение
}
}
2. Таймер-логгер (фоновое логирование)
public class BackgroundLogger {
public static void startDaemonLogger() {
Thread loggerThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("[LOG] Статус: " + System.currentTimeMillis());
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
loggerThread.setName("Background-Logger");
loggerThread.setDaemon(true);
loggerThread.start();
}
public static void main(String[] args) throws InterruptedException {
startDaemonLogger();
System.out.println("Основная работа");
Thread.sleep(12000);
System.out.println("Основная работа завершена");
// Логгер прекратит работать, когда завершится main
}
}
3. Монитор памяти
public class MemoryMonitor {
public static void startMonitoring() {
Thread monitorThread = new Thread(() -> {
Runtime runtime = Runtime.getRuntime();
while (!Thread.currentThread().isInterrupted()) {
try {
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
double percentage = (usedMemory * 100) / maxMemory;
if (percentage > 80) {
System.out.println("⚠️ ПРЕДУПРЕЖДЕНИЕ: память на " + percentage + "%");
}
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
monitorThread.setName("Memory-Monitor");
monitorThread.setDaemon(true);
monitorThread.start();
}
public static void main(String[] args) throws InterruptedException {
startMonitoring();
// Мониторинг работает в фоне
}
}
Сравнение сценариев завершения
Сценарий 1: Есть обычные потоки
public class Scenario1 {
public static void main(String[] args) throws InterruptedException {
Thread normal = new Thread(() -> Thread.sleep(10000));
Thread daemon = new Thread(() -> Thread.sleep(10000));
normal.start();
daemon.setDaemon(true);
daemon.start();
System.out.println("Main завершён");
}
}
// Результат: JVM ждёт 10 сек, пока завершится normal
// Демон также завершится в течение 10 сек
Сценарий 2: Остались только демоны
public class Scenario2 {
public static void main(String[] args) throws InterruptedException {
Thread daemon = new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
System.out.println("Демон: итерация " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Демон прерван");
}
});
daemon.setDaemon(true);
daemon.start();
System.out.println("Main завершён");
}
}
// Результат: Main завершается сразу
// JVM выходит, демон останавливается принудительно
// Вывод: Main завершён (и демон больше не будет работать)
Проверка, является ли поток демоном
public class CheckDaemonStatus {
public static void main(String[] args) {
Thread normalThread = new Thread(() -> {});
Thread daemonThread = new Thread(() -> {});
daemonThread.setDaemon(true);
System.out.println("Normal: " + normalThread.isDaemon()); // false
System.out.println("Daemon: " + daemonThread.isDaemon()); // true
System.out.println("Main: " + Thread.currentThread().isDaemon()); // false
}
}
Важные замечания
1. Демоны не гарантируют остановку при выходе
public class DaemonNoGuarantee {
public static void main(String[] args) {
Thread daemon = new Thread(() -> {
System.out.println("Демон начал");
System.out.println("Демон работает");
System.out.println("Демон завершился");
});
daemon.setDaemon(true);
daemon.start();
// Может вывести только "Демон начал" или ничего,
// если JVM выходит до завершения потока
}
}
2. Ресурсы демонов
public class DaemonResources {
public static void main(String[] args) throws Exception {
Thread daemon = new Thread(() -> {
// Откроем ресурс (файл, БД соединение)
try {
FileWriter fw = new FileWriter("daemon_log.txt");
fw.write("Демон работает");
// Файл НЕ закроется, если JVM выходит!
Thread.sleep(10000);
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
});
daemon.setDaemon(true);
daemon.start();
// Выходим сразу
}
}
// ✗ ПРОБЛЕМА: файл может остаться открытым!
Решение: Используй try-with-resources или finally для закрытия ресурсов.
Когда НЕ использовать демоны
- ✗ Для работы с важными данными (БД, файлы)
- ✗ Когда нужна гарантия завершения
- ✗ Для критичной логики приложения
Когда использовать демоны
- ✓ Фоновый мониторинг
- ✓ Сборка мусора
- ✓ Периодические проверки
- ✓ Вспомогательные задачи
Заключение
Daemon threads — это инструмент для фоновых задач, которые не должны препятствовать завершению приложения. Помни:
- setDaemon(true) — перед start()
- JVM выходит, если остались только демоны
- Не гарантируют завершение при выходе
- Используй для фоновых задач, а не критичной логики