Java Thread
1. Introduction
The Thread class in Java represents the smallest unit of execution in a program. It allows concurrent execution of multiple tasks, sharing the same process memory space. Understanding Thread is essential for mastering Java concurrency, as it forms the foundation for higher-level frameworks like ExecutorService, ForkJoinPool, and Virtual Threads.
2. Thread Fundamentals
Every thread in Java is an independent path of execution within a process. A program begins with the main thread, which can create additional threads to perform tasks concurrently.
Key Properties
- Each thread runs a single task defined by a
Runnableor by overridingrun(). - Threads share memory but maintain independent call stacks.
- Thread scheduling and execution order are managed by the JVM and OS scheduler, not the developer.
Example
class Worker extends Thread {
public void run() {
System.out.println("Task running in: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Worker worker = new Worker();
worker.start(); // Start the thread
System.out.println("Main thread: " + Thread.currentThread().getName());
}
}
💡 Note: Calling start() creates a new thread, while calling run() directly executes the method in the current thread — no new thread is created.
3. Thread Lifecycle
A thread’s lifecycle is managed internally by the JVM. The states are defined in the Thread.State enum.
| State | Description |
|---|---|
| NEW | Thread object is created but not yet started. |
| RUNNABLE | Thread is ready or running — actual execution determined by scheduler. |
| BLOCKED | Thread is waiting to acquire a monitor lock. |
| WAITING | Thread is waiting indefinitely for another thread’s action (e.g., wait()). |
| TIMED_WAITING | Thread is waiting for a specific time (e.g., sleep(1000)). |
| TERMINATED | Thread has completed execution or exited due to an exception. |
Example: Visualizing Lifecycle
Thread t = new Thread(() -> System.out.println("Running"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE or TERMINATED (depends on timing)
⚙️ Compiler Behavior: The JVM tracks thread state transitions internally; developers cannot manually change them.
4. Thread Priority
Thread priorities influence the scheduler’s choice of which thread to run first. However, they are merely hints — the JVM may ignore them depending on the underlying OS.
| Constant | Value | Meaning |
|---|---|---|
MIN_PRIORITY | 1 | Lowest priority |
NORM_PRIORITY | 5 | Default priority |
MAX_PRIORITY | 10 | Highest priority |
Example
Thread t1 = new Thread(() -> System.out.println("Low priority"));
Thread t2 = new Thread(() -> System.out.println("High priority"));
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
⚠️ Edge Case: High priority does not guarantee earlier execution — OS-level scheduling can override it.
5. Thread Constructors
Threads can be constructed in several ways depending on the execution target and naming.
| Constructor | Description |
|---|---|
Thread() | Creates a new thread with default name and no target. |
Thread(Runnable target) | Executes the target’s run() method when started. |
Thread(Runnable target, String name) | Same as above but assigns a custom name. |
Thread(String name) | Creates a named thread with no target. |
Example
Runnable task = () -> System.out.println(Thread.currentThread().getName() + " executing task.");
Thread t = new Thread(task, "Worker-1");
t.start();
💡 Best Practice: Always assign meaningful thread names to simplify debugging and log tracing.
6. Important Static Methods
The Thread class provides several static utility methods to manage thread behavior globally.
| Method | Description |
|---|---|
currentThread() | Returns a reference to the currently executing thread. |
sleep(long millis) | Temporarily pauses execution for the specified time. (doesn’t release locks) |
yield() | Hints the scheduler to give other threads a chance to execute. |
onSpinWait() | Optimized CPU spin-wait hint introduced in Java 9. |
Example
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " - tick " + i);
Thread.sleep(500); // Pause for 0.5 seconds
}
7. The Interrupt Mechanism
Thread interruption is a cooperative mechanism to stop or signal a running thread. It doesn’t forcibly terminate a thread — instead, it sets an internal flag that the thread can check.
Example
class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Working...");
}
System.out.println("Stopped safely.");
});
worker.start();
Thread.sleep(1000);
worker.interrupt(); // Request stop
}
}
💡 Key Concept: The interrupt() method sets the interrupt flag. Methods like sleep() or wait() respond by throwing InterruptedException and clearing the flag.
Distinguishing interrupt() vs interrupted()
| Method | Type | Effect |
|---|---|---|
interrupt() | Instance method | Sets the interrupt flag of the target thread. |
interrupted() | Static method | Checks and clears the interrupt flag of the current thread. |
⚠️ Edge Case: Calling interrupt() on another thread doesn’t guarantee immediate termination — the target thread must cooperatively check its status.
8. Edge Cases and Internal Behavior
- Calling
start()twice: ThrowsIllegalThreadStateException— a thread can only start once. - Thread safety: Multiple threads writing shared data without synchronization can lead to race conditions.
- Memory visibility: Without synchronization, changes made by one thread may not be visible to another.
- Finalization: Once a thread terminates, it cannot be restarted.
⚙️ Compiler Behavior Insight: The JVM uses a combination of native and managed calls for thread scheduling. Thread objects remain in memory until garbage collected, even after termination.
⚡️ wait() vs sleep()
| 🔹Aspect | 🔹wait() | 🔹sleep() |
|---|---|---|
| Purpose | Used for thread coordination — allows one thread to pause until another thread notifies it. | Used for timed pauses or delays, usually to throttle execution or simulate waiting. |
| Lock release | ✅ Yes, releases the monitor lock of the object it’s called on. | ❌ No, thread holds the lock (if any) while sleeping. |
| Requires synchronized block | ✅ Yes, must be called inside a synchronized block or method, else IllegalMonitorStateException. | ❌ No, can be called anywhere. |
| Belongs to | Object class (because it’s tied to monitor/lock mechanics). | Thread class (because it’s purely about pausing execution). |
| How thread wakes up | By another thread calling notify(), notifyAll(), or if the thread is interrupted. | Automatically after the sleep duration ends or if interrupted. |
| Throws exception | InterruptedException | InterruptedException |
| Common use case | Producer–Consumer pattern, thread coordination, or waiting for a condition to change. | Throttling loops, retry delays, animation timing, etc. |
| Changes thread state | ✅ From RUNNABLE → WAITING / TIMED_WAITING depending on wait() overload. | ✅ From RUNNABLE → TIMED_WAITING (for specified sleep duration). |
| Monitor behavior | Releases the monitor and lets other threads acquire it. | Keeps the monitor locked — other threads are blocked if they need the same lock. |
| Control location | Works on an object monitor, so it depends on synchronized object’s lock. | Works on the current thread only. |
| Resumes execution | After being notified or interrupted (and reacquiring the monitor). | After time elapses or interruption. |
⚡️ notify() vs notifyAll()
| 🔹Aspect | 🔹notify() | 🔹notifyAll() |
|---|---|---|
| Purpose | Wakes up one single thread that is waiting on the object's monitor. | Wakes up all threads waiting on the object's monitor. |
| Belongs to | Object class | Object class |
| Requires synchronization | ✅ Yes — must be called within a synchronized block or method that locks the same object. | ✅ Yes — same as notify(). |
| Thread state affected | One thread moves from WAITING → BLOCKED (to re-acquire the lock) → RUNNABLE. | All waiting threads move from WAITING → BLOCKED (to re-acquire the lock), but only one thread actually proceeds first (others compete). |
| Lock behavior | Does not release the lock immediately; lock is released after synchronized block ends. | Same — lock is released only when synchronized block exits. |
| Use case | When you know exactly one thread needs to be resumed (e.g., single consumer for a single produced item). | When multiple waiting threads might need to re-check a condition (e.g., multiple consumers waiting for items). |
| Performance | Slightly more efficient — fewer threads are woken. | Can be less efficient — may cause “thundering herd problem” (many threads waking unnecessarily). |
| Risk / Edge Case | ❗ If multiple threads are waiting but you call only notify(), others may starve indefinitely. | ✅ Safer when multiple waiting threads depend on shared conditions. |
| Thread scheduling | The thread to wake up is chosen arbitrarily by JVM — no guarantee which one. | All waiting threads are awakened, but scheduling order is still unpredictable. |
| State change | Changes only one thread from WAITING → BLOCKED (for monitor) → RUNNABLE. | Changes all waiting threads from WAITING → BLOCKED (for monitor) → RUNNABLE. |