9.1 Thread Fundamentals, Creation, and Lifecycle
Key Takeaways
- Platform threads in Java map 1:1 to operating system kernel threads and can be created by subclassing Thread, implementing Runnable, or using the Java 21 Thread.ofPlatform() builder API.
- The JVM thread lifecycle consists of six distinct states defined in Thread.State: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, and TERMINATED.
- Calling start() transitions a thread to RUNNABLE and spawns a new OS execution context; invoking run() directly merely executes the method synchronously on the caller thread without starting a new thread.
- Thread interruption is a cooperative signaling mechanism: thread.isInterrupted() queries the status without clearing it, whereas static Thread.interrupted() tests and clears the current thread interrupt flag.
- Daemon threads execute background tasks without preventing JVM termination when all non-daemon user threads finish; daemon status must be configured via setDaemon(true) before start() is called.
Thread Fundamentals, Creation, and Lifecycle
Concurrency is at the core of enterprise Java development and is heavily tested on the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam. Understanding how the Java Virtual Machine (JVM) manages execution threads, transitions between thread states, coordinates cooperative task execution, and terminates processes is essential for passing the exam and writing robust software.
1. Concurrency, Parallelism, and Threads
Before diving into code, it is important to clarify key architectural definitions:
- Process: An independent, isolated execution environment created by the operating system with its own private address space, memory pages, and system resources.
- Thread: The smallest schedulable unit of execution within a process. All threads belonging to a process share the process's heap memory, static variables, and open file descriptors, but each thread maintains its own private Program Counter (PC), native stack, and call stack (local variables).
- Concurrency vs. Parallelism:
- Concurrency is the composition of independently executing processes or threads. It means managing multiple tasks by interleaving their execution over time (even on a single-core CPU via time-slicing).
- Parallelism is the simultaneous execution of multiple computational tasks at the exact same physical instant across multiple distinct CPU cores or processors.
In traditional Java (prior to Java 21 virtual threads), every java.lang.Thread instance represents a Platform Thread (also called a carrier or kernel thread) that corresponds directly to a 1:1 mapping with an Operating System (OS) kernel thread.
2. Thread Creation Mechanisms
In Java SE 21, there are three primary approaches to define and start platform threads:
Approach A: Subclassing java.lang.Thread
You can extend java.lang.Thread and override its public void run() method:
public class WorkerThread extends Thread {
public WorkerThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("Executing on thread: " + getName());
}
}
// Usage
WorkerThread worker = new WorkerThread("Worker-1");
worker.start(); // Spawns a new OS thread and invokes run() asynchronously
[!WARNING] Subclassing
Threadis generally discouraged in modern application design because Java only permits single class inheritance. ExtendingThreadprevents the class from extending any other domain superclass, tightens coupling between the unit of work and the execution mechanism, and wastes object overhead.
Approach B: Implementing java.lang.Runnable (Recommended Classic Approach)
Runnable is a @FunctionalInterface defining a single abstract method (SAM): public void run(). By passing a Runnable task to a Thread constructor, you cleanly separate the task to be performed from the threading execution mechanism:
// 1. Implementing Runnable via class
public class DatabaseTask implements Runnable {
@Override
public void run() {
System.out.println("Processing database batch...");
}
}
// 2. Lambda expression (anonymous Runnable)
Runnable task = () -> System.out.println("Running task on: " + Thread.currentThread().getName());
// 3. Method reference
Runnable cleanup = DatabaseTask::cleanupStaleConnections;
// Starting the thread
Thread t1 = new Thread(new DatabaseTask(), "DB-Thread");
Thread t2 = new Thread(task, "Lambda-Thread");
t1.start();
t2.start();
Approach C: The Java 21 Fluent Builder API (Thread.ofPlatform())
Java 21 introduces fluent builder APIs on the Thread class to construct platform and virtual threads with rich configurations (name prefixes, start indices, daemon flags, uncaught exception handlers):
// Create and immediately start a platform thread
Thread t1 = Thread.ofPlatform()
.name("order-processor-", 1) // Names: order-processor-1, order-processor-2...
.daemon(false)
.priority(Thread.NORM_PRIORITY)
.start(() -> System.out.println("Processing on " + Thread.currentThread().getName()));
// Create an unstarted thread instance
Thread t2 = Thread.ofPlatform()
.name("batch-worker")
.unstarted(() -> System.out.println("Batch execution started"));
t2.start(); // Explicit manual start
3. The Crucial Exam Trap: start() vs. run()
The difference between calling start() and run() is one of the most frequently tested topics on the 1Z0-830 exam:
| Feature | thread.start() | thread.run() |
|---|---|---|
| Execution Context | Requests the JVM and OS to allocate a new call stack and schedule a new thread. | Executes the run() method synchronously on the current calling thread. |
| New Thread Created? | Yes | No (runs on main or whichever thread invoked it) |
| Multiple Invocations | Throws java.lang.IllegalThreadStateException if called more than once. | Can be invoked multiple times like any ordinary Java method. |
Thread t = new Thread(() -> {
System.out.println("Inside run(): " + Thread.currentThread().getName());
});
t.run(); // PRINTS: "Inside run(): main" (Synchronous method call!)
t.start(); // PRINTS: "Inside run(): Thread-0" (Asynchronous new thread!)
// t.start(); // RUNTIME ERROR: Throws IllegalThreadStateException!
4. The 6 JVM Thread Lifecycle States (Thread.State)
The JVM manages each thread according to the java.lang.Thread.State enumeration. You can query a thread's current state at any time by calling thread.getState().
| State | Description | Typical Transitions In / Out |
|---|---|---|
NEW | The thread instance has been constructed, but start() has not yet been invoked. | Transitions to RUNNABLE when start() is called. |
RUNNABLE | The thread is executing in the JVM or is ready and waiting for CPU allocation from the OS scheduler. This state encompasses both running and ready-to-run states, as well as waiting for OS resources like socket or file I/O. | Transitions from NEW; transitions to BLOCKED, WAITING, TIMED_WAITING, or TERMINATED. |
BLOCKED | The thread is suspended, waiting to acquire an intrinsic monitor lock to enter or re-enter a synchronized method or statement block. | Transitions to RUNNABLE once the monitor lock is acquired. |
WAITING | The thread is waiting indefinitely for another thread to perform a specific notification action. It does not consume CPU cycles. | Enters via: Object.wait(), Thread.join(), or LockSupport.park(). Exits to BLOCKED or RUNNABLE upon notify(), notifyAll(), or target thread termination. |
TIMED_WAITING | The thread is waiting for another thread for up to a specified maximum duration. | Enters via: Thread.sleep(millis), Object.wait(millis), Thread.join(millis), LockSupport.parkNanos(), or LockSupport.parkUntil(). Exits when timeout expires or upon notification. |
TERMINATED | The thread has completed execution because its run() method returned normally or threw an uncaught exception. | Once TERMINATED, a thread can never be restarted. |
5. Thread Coordination and Control Primitives
A. Thread.sleep(long millis) and TimeUnit
Thread.sleep() pauses the current thread for a specified duration without relinquishing any CPU locks or monitors it currently holds:
try {
Thread.sleep(2000); // Sleep for 2000 milliseconds
// Or more readably:
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// Thrown if another thread interrupts the sleeping thread
Thread.currentThread().interrupt(); // Restore interrupt status flag
}
[!IMPORTANT]
Thread.sleep()is a static method. Callingt.sleep(1000)on a thread referencetdoes not put threadtto sleep; it puts the currently executing thread to sleep!
B. Thread.join()
The join() method allows one thread to pause its execution and wait until another target thread has completed its execution (TERMINATED state):
Thread worker = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Worker done.");
});
worker.start();
System.out.println("Main waiting for worker...");
worker.join(); // Main thread transitions to WAITING until worker terminates
// worker.join(500); // TIMED_WAITING: waits at most 500ms
System.out.println("Worker finished. Main resumes.");
C. Thread.yield()
Thread.yield() provides a non-binding heuristic hint to the operating system thread scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint. It does not throw InterruptedException and does not block the thread.
6. Cooperative Thread Interruption
Java does not support preemptive thread termination (methods like stop(), suspend(), and resume() are deprecated for removal and unsafe). Instead, Java employs a cooperative interruption mechanism using an internal boolean interrupt status flag.
The Interruption API
thread.interrupt()(Instance method):- Sets the interrupt status flag of the target thread to
true. - If the target thread is currently blocked in a call to
Thread.sleep(),Object.wait(), orThread.join(), the blocking call is aborted, anInterruptedExceptionis thrown, and the interrupt status flag is automatically cleared (reset tofalse)!
- Sets the interrupt status flag of the target thread to
thread.isInterrupted()(Instance method):- Returns
trueif the target thread has been interrupted; does not clear or modify the flag.
- Returns
Thread.interrupted()(Static method):- Tests whether the current thread has been interrupted and clears the interrupt status flag (sets it to
false).
- Tests whether the current thread has been interrupted and clears the interrupt status flag (sets it to
// Standard cooperative cancellation loop pattern
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// Perform work unit...
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("Interrupted during sleep! Flag was cleared.");
// Crucial: Restore interrupt status or exit loop cleanly
Thread.currentThread().interrupt(); // Restore flag
break; // Terminate worker cleanly
}
}
System.out.println("Worker cleaned up and exiting.");
});
worker.start();
worker.interrupt(); // Signal thread to stop
7. Daemon Threads
Threads in Java are categorized into two types:
- User (Non-Daemon) Threads: High-priority application threads. The JVM will keep running as long as at least one non-daemon thread is alive.
- Daemon Threads: Background service providers (e.g., garbage collection, finalizer, JVM telemetry). When all user threads terminate, the JVM immediately halts and terminates, abruptly killing all running daemon threads without executing remaining
finallyblocks or flushes.
Configuration Rules:
- A thread inherits the daemon status of the thread that created it.
- To configure a platform thread as a daemon, invoke
thread.setDaemon(true)strictly before callingthread.start(). - Calling
setDaemon(true)on a thread that is already running throwsIllegalThreadStateException.
Thread daemonWorker = new Thread(() -> {
while (true) {
System.out.println("Background logging ping...");
try { Thread.sleep(500); } catch (InterruptedException e) { break; }
}
});
daemonWorker.setDaemon(true); // Must be called BEFORE start()
daemonWorker.start();
// When the main thread completes, the JVM exits immediately!
8. Thread Priority and Uncaught Exception Handlers
- Thread Priorities: Integer values ranging from
Thread.MIN_PRIORITY(1) toThread.MAX_PRIORITY(10), defaulting toThread.NORM_PRIORITY(5). Priorities serve only as hints to the OS scheduler and do not guarantee execution order. Thread.UncaughtExceptionHandler: An interface to capture uncaught exceptions escaping from a thread'srun()method:
Thread t = new Thread(() -> {
throw new RuntimeException("Fatal failure in worker!");
});
t.setUncaughtExceptionHandler((thread, throwable) -> {
System.err.println("Thread " + thread.getName() + " threw: " + throwable.getMessage());
});
t.start();
What is the output of compiling and running the following Java program?
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.print("Running ");
});
t.run();
t.start();
}
}
A developer creates a platform thread and attempts to configure its daemon status after launching it:
What happens when this code is executed?Thread t = new Thread(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) {}
});
t.start();
t.setDaemon(true);
A worker thread is sleeping inside a try-catch block when another thread calls worker.interrupt(). What occurs inside the worker thread?
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().isInterrupted());
}
Which of the following describes the exact condition that causes a Java thread to enter the BLOCKED state defined in java.lang.Thread.State?