In Java’s multithreaded environment, threads can be categorized as either daemon threads or non-daemon threads. The word “daemon” comes from Greek mythology, where daemons were spiritual beings that performed services for others. In the context of Java threads, daemon threads are low-priority threads that are intended to serve and support non-daemon threads.
What are Daemon Threads?
Daemon threads are low-priority threads that are intended to serve and support non-daemon threads. They are often used for background tasks such as garbage collection, monitoring, or providing services to non-daemon threads.
The key characteristic of daemon threads is that they do not prevent the Java Virtual Machine (JVM) from exiting when the only remaining threads are daemon threads.
What are Non-Daemon Threads?
Non-daemon threads, also known as user threads or regular threads, are the default type of threads created in Java. These threads are responsible for executing the main logic of an application. As long as there is at least one non-daemon thread running or waiting, the JVM will continue to run.
JVM Behavior with Daemon and Non-Daemon Threads
The JVM’s behavior is closely tied to the presence and state of non-daemon threads:
- If there are any non-daemon threads running or waiting: The JVM will continue running indefinitely, regardless of the presence or state of daemon threads.
- If all non-daemon threads have finished their execution: The JVM will terminate, even if there are still daemon threads running or waiting.
Daemon threads are designed to support non-daemon threads and are not meant to keep the JVM running indefinitely on their own. When the last non-daemon thread finishes, the JVM will automatically terminate, and any remaining daemon threads will be abruptly stopped.
Joining Daemon and Non-Daemon Threads
In Java, you can use the join()
method to wait for a thread to finish its execution. However, the behavior of join()
differs for daemon and non-daemon threads:
- Joining a non-daemon thread: When you call
join()
on a non-daemon thread, the calling thread will wait indefinitely until the non-daemon thread finishes its execution. - Joining a daemon thread: When you call
join()
on a daemon thread, the calling thread will wait until the daemon thread finishes its execution or until the JVM terminates, whichever comes first.
It’s important to note that if you call join()
on a daemon thread from a non-daemon thread, and the JVM terminates before the daemon thread finishes, the calling non-daemon thread will be interrupted with an InterruptedException
.
Please not here “calling thread”, I mean the thread that calls the join()
method on another thread. It refers to the thread that is executing the join()
call.
For example, let’s say you have the following code:
Thread t1 = new Thread(() -> {
// Thread t1's code
});
t1.start();
// Some other code in the main thread
t1.join(); // The main thread is calling join() on t1
In this case, the “calling thread” is the main thread, which is executing the t1.join()
statement. So, when I say:
- “Joining a non-daemon thread: When you call
join()
on a non-daemon thread, the calling thread will wait indefinitely until the non-daemon thread finishes its execution.”
It means that if t1
is a non-daemon thread, the main thread (the calling thread) will wait indefinitely (block) until t1
finishes its execution.
Similarly, if we have:
Thread t2 = new Thread(() -> {
// Thread t2's code
});
t2.setDaemon(true); // t2 is a daemon thread
t2.start();
// Some other code in the main thread
t2.join(); // The main thread is calling join() on t2
And I say:
- “Joining a daemon thread: When you call
join()
on a daemon thread, the calling thread will wait until the daemon thread finishes its execution or until the JVM terminates, whichever comes first.”
It means that if t2
is a daemon thread, the main thread (the calling thread) will wait for t2
to finish its execution, or until the JVM terminates (whichever happens first). If the JVM terminates before t2
finishes, the join()
call will be interrupted.
So, the “calling thread” simply refers to the thread that is executing the join()
method on another thread. It could be the main thread, or it could be another non-daemon thread, depending on the context in which the join()
call is made.
Example: Daemon Thread and Main Thread Termination
Let’s explore an example to understand the behavior of daemon and non-daemon threads when the main thread finishes its execution.
public class ThreadExample {
public static void main(String[] args) {
// Non-daemon thread
Thread nonDaemonThread = new Thread(() -> {
try {
Thread.sleep(5000); // Simulating some work
System.out.println("Non-daemon thread finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
nonDaemonThread.start();
// Daemon thread
Thread daemonThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000); // Simulating background task
System.out.println("Daemon thread running");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // Mark as a daemon thread
daemonThread.start();
// Wait for the non-daemon thread to finish
try {
nonDaemonThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread finished");
}
}
In this example, we create two threads: a non-daemon thread (nonDaemonThread
) and a daemon thread (daemonThread
). The non-daemon thread simulates some work by sleeping for 5 seconds, while the daemon thread runs an infinite loop, printing a message every second (simulating a background task).
The main thread waits for the non-daemon thread to finish by calling join()
. When the non-daemon thread finishes, the main thread prints a message and exits.
Output (truncated):
Daemon thread running
Daemon thread running
Daemon thread running
Daemon thread running
Daemon thread running
Non-daemon thread finished
Main thread finished
As you can see, the daemon thread continues running until the non-daemon thread finishes. Once the non-daemon thread finishes and the main thread exits, the JVM terminates, abruptly stopping the daemon thread.
On the other hand, if the nonDaemonThread
was marked as a daemon thread, the JVM would have terminated immediately after the main thread finished, without waiting for the daemon thread to complete.
public class ThreadExample {
public static void main(String[] args) {
// Non-daemon thread
Thread nonDaemonThread = new Thread(() -> {
try {
Thread.sleep(5000); // Simulating some work
System.out.println("Non-daemon thread finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
nonDaemonThread.start();
// Daemon thread
Thread daemonThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000); // Simulating background task
System.out.println("Daemon thread running");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // Mark as a daemon thread
daemonThread.start();
// Wait for the non-daemon thread to finish
try {
nonDaemonThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Mark the non-daemon thread as a daemon thread
nonDaemonThread.setDaemon(true);
System.out.println("Main thread finished");
}
}
In this example, we have two threads: nonDaemonThread
and daemonThread
. Initially, nonDaemonThread
is a non-daemon thread, and daemonThread
is marked as a daemon thread.
The main thread waits for nonDaemonThread
to finish by calling join()
. After nonDaemonThread
finishes, we mark it as a daemon thread using nonDaemonThread.setDaemon(true);
. Finally, the main thread prints “Main thread finished” and exits.
Output:
Daemon thread running
Daemon thread running
Daemon thread running
Daemon thread running
Daemon thread running
Non-daemon thread finished
Main thread finished
You’ll notice that the output doesn’t include any additional messages from the daemonThread
. This is because once the main thread finishes, the JVM terminates immediately, without waiting for the daemonThread
to complete, as it is the only remaining thread (and it’s a daemon thread).
Even though the daemonThread
was simulating a background task and printing messages every second, it was abruptly stopped when the JVM terminated after the main thread finished.
This example illustrates that if a non-daemon thread is marked as a daemon thread (like nonDaemonThread
in this case), the JVM will not wait for that thread to complete its execution after the main thread finishes. Instead, the JVM will terminate immediately, stopping all remaining daemon threads.
Conclusion
In Java, daemon threads are designed to support non-daemon threads and do not prevent the JVM from exiting when they are the only remaining threads. Non-daemon threads, on the other hand, keep the JVM running as long as at least one of them is running or waiting. Understanding the behavior of daemon and non-daemon threads, as well as the implications of joining them, is crucial for managing thread lifecycle and ensuring proper application termination.
By utilizing daemon threads for background tasks and non-daemon threads for the main application logic, developers can leverage the power of multithreading while ensuring that the JVM exits gracefully when the main application has completed its work.