← Назад к вопросам

Что такое потоки-демоны (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 — это инструмент для фоновых задач, которые не должны препятствовать завершению приложения. Помни:

  1. setDaemon(true) — перед start()
  2. JVM выходит, если остались только демоны
  3. Не гарантируют завершение при выходе
  4. Используй для фоновых задач, а не критичной логики