Blog Java

Understanding Daemon Threads and Non-Daemon Threads in Java

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:

Java
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:

Java
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.

Java
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):

Java
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.

Java
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:

Java
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.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over