At the heart of this model lies the interaction between the Java Virtual Machine (JVM) and the operating system. While Java provides a platform-independent abstraction for threads, the actual execution is tightly coupled with native OS-level thread management.
JVM Threads vs OS Threads
Understanding JVM Threads
In Java, when you create a thread usingnew Thread(), you are working with a JVM thread. These threads are managed by the JVM and provide a consistent programming interface regardless of the underlying platform.
Thread t = new Thread(() -> {
System.out.println("Running in JVM thread");
});
t.start();
The JVM is responsible for handling thread lifecycle, synchronization, and coordination between threads. However, it does not execute threads directly on the hardware.
Understanding OS Threads
The actual execution of threads is handled by the operating system. These are known as native threads or OS threads. The operating system is responsible for scheduling threads on CPU cores, performing context switching, and managing execution priorities.Each OS thread is a low-level construct that directly interacts with the CPU.
JVM to OS Thread Mapping
Modern Java implementations use a one-to-one threading model, meaning each JVM thread maps directly to an OS thread. This allows Java applications to fully utilize multi-core processors and achieve true parallelism.Earlier JVMs experimented with green threads, where threads were managed entirely by the JVM without OS involvement. However, this approach had limitations and is no longer used in modern Java.
The one-to-one mapping ensures that when you create multiple threads in Java, they can be executed in parallel by the operating system, depending on available CPU resources.
User Threads vs Daemon Threads
User Threads
User threads are the primary threads that perform the main tasks of an application. By default, every thread created in Java is a user thread unless specified otherwise.The JVM continues to run as long as at least one user thread is active. This means that user threads prevent the application from shutting down.
Thread userThread = new Thread(() -> {
System.out.println("User thread running");
});
userThread.start();
Daemon Threads
Daemon threads are background threads that provide supporting services to user threads. Examples include garbage collection, monitoring, and housekeeping tasks.The JVM does not wait for daemon threads to finish execution. Once all user threads have completed, the JVM terminates, and any remaining daemon threads are stopped automatically.
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread running...");
}
});
daemonThread.setDaemon(true);
daemonThread.start();
Key Differences
The distinction between user and daemon threads is crucial for application design. While user threads define the core functionality, daemon threads support background operations. Misusing daemon threads can lead to unexpected termination of important tasks if all user threads finish execution prematurely.Thread Lifecycle in Java
Every thread in Java goes through a well-defined lifecycle. Understanding these states is essential for debugging and designing concurrent applications.1. NEW
A thread is in the NEW state when it is created but not yet started. At this stage, the thread object exists, but it has not been scheduled for execution. Thread t = new Thread(() -> System.out.println("Hello"));
2. RUNNABLE
Oncestart() is called, the thread moves to the RUNNABLE state. In this state, the thread is ready to run and waiting for CPU time. The OS scheduler decides when it will actually execute.
t.start();
Note that RUNNABLE includes both running and ready-to-run states.
3. BLOCKED
A thread enters the BLOCKED state when it is waiting to acquire a monitor lock to enter a synchronized block or method. synchronized (this) {
// critical section
}
If another thread holds the lock, the current thread will remain blocked until the lock becomes available.
4. WAITING
A thread enters the WAITING state when it is waiting indefinitely for another thread to perform a particular action. synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
The thread will remain in this state until it is notified using notify() or notifyAll().
5. TIMED_WAITING
A thread enters the TIMED_WAITING state when it waits for a specified amount of time. synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Other methods like join(long millis) and wait(long timeout) also lead to this state.
6. TERMINATED
A thread reaches the TERMINATED state when its execution is complete. Once terminated, a thread cannot be restarted. Thread t2 = new Thread(() -> {
System.out.println("Thread finished");
});
t2.start();
After the run() method completes, the thread transitions to the terminated state.
The lifecycle of a thread can be summarized as follows: a thread is created in the NEW state, moves to RUNNABLE when started, may enter BLOCKED, WAITING, or TIMED_WAITING based on conditions, and finally transitions to TERMINATED after execution completes.
The Java Thread Model provides a powerful abstraction over the complexities of operating system threads. By understanding the relationship between JVM threads and OS threads, developers can better appreciate how Java achieves concurrency and parallelism.
The distinction between user threads and daemon threads helps in designing applications that behave correctly during shutdown. Meanwhile, a deep understanding of the thread lifecycle enables developers to debug issues and optimize performance.
As we move forward in this series, we will explore how to create threads in different ways and how to control their execution effectively, laying the groundwork for advanced concurrency techniques.
Join the discussion