🧵 Concurrency Fundamentals · Advanced

Daemon threads in Java

Daemon threads don't keep the JVM alive.

🧩 The mysteryOne program finishes main() but the JVM keeps running for an hour. Another exits instantly and silently loses its last log lines. Same root cause: daemon threads.

What keeps the JVM alive

The JVM exits when all non-daemon (user) threads have finished. Daemon threads are background helpers that don't count: when the last user thread ends, the JVM shuts down and simply abandons the daemons, wherever they are.

🔮 Predict it

Your turn

A daemon thread creates a child thread. Is the child a daemon?

Thread parent = new Thread(() -> {
    Thread child = new Thread(() -> {});
    System.out.println(child.isDaemon());
});
parent.setDaemon(true);
parent.start();
parent.join();
  1. true
  2. false
  3. Throws IllegalThreadStateException
Show the answer

true. A new platform thread inherits daemon status from the thread that creates it. main is a user thread, so threads made by main start as non-daemon (isDaemon() is false until you call setDaemon(true)). This child was made by a daemon.

Decide before start()

setDaemon(true) must be called before start(). Calling it on a thread that is already alive throws IllegalThreadStateException.

Thread flusher = new Thread(this::flushLogs);
flusher.setDaemon(true);   // before start!
flusher.start();
⚠️ The trap

finally isn't guaranteed

A CLI starts a daemon that flushes logs every second; main prints its result and returns. The last log lines go missing: the JVM exits and stops the daemon mid-flush. A daemon's finally blocks may never run. If work must complete, have main join() the thread, or make it a non-daemon thread.

🤔 Think first

The forgotten pool

You create Executors.newFixedThreadPool(4), run a few tasks, and forget shutdown(). main ends. Does the JVM exit?

Think about it, then reveal the answer

No. Pool workers are non-daemon by default, so even idle workers keep the JVM alive. That's why a forgotten pool makes a program hang at the end. Contrast: virtual threads are always daemon, so one blocked on a socket read won't keep the JVM running.

💼 In the real world

Choosing wisely

Use daemon threads only for work that's safe to drop: refreshing a cache, sampling metrics, a heartbeat. Anything that must finish, like writing a report or flushing data, belongs on a user thread that you join() or shut down properly. Mixing these up gives you either hanging tools or lost data.

Key takeaways

  1. The JVM exits when only daemon threads remain
  2. setDaemon() must be called before start()
  3. Daemons can stop abruptly — finally blocks aren't guaranteed
  4. New threads inherit daemon status; virtual threads are always daemon
🤯 Did you know?

Virtual threads can't be anything but daemons: calling setDaemon(false) on one throws IllegalArgumentException.

Practice questions

What does this print?

Thread t = new Thread(() -> {});
System.out.println(t.isDaemon());
t.setDaemon(true);
System.out.println(t.isDaemon());
  1. false true
  2. true true
  3. false false
  4. true false
Check your answer

false true. A new platform thread inherits daemon status from the thread that creates it. main is a user thread, so t starts as non-daemon until setDaemon(true).

A CLI tool starts a daemon thread that flushes logs to disk every second. main returns right after printing its result. The last log lines are often missing. Why?

  1. The JVM exits once main ends, abandoning the daemon mid-flush
  2. Daemon threads get no CPU while main is running
  3. Background threads can't write files
  4. The garbage collector clears pending log buffers
Check your answer

The JVM exits once main ends, abandoning the daemon mid-flush. Only user threads keep the JVM alive. When main finishes, the JVM shuts down and simply stops the daemon thread, wherever it is.

Next: four strategies that make shared state safe, and why the best one is not sharing at all.