Study guide
9.1 Thread Fundamentals, Creation, and Lifecycle
Oracle Java SE 21 Developer 1Z0-830
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Java Basics, Primitive Types, and Operators
1.1 Program Structure, Primitives, Literals, Scopes, and var 1.2 Operators, Precedence, and Type Casting 1.3 Strings, Text Blocks, and StringBuilder 1.4 Date and Time APIs (java.time)
2 Chapter 2: Controlling Program Flow & Pattern Matching
2.1 Conditional Logic: if-else and Ternary Operators 2.2 Switch Statements and Switch Expressions 2.3 Pattern Matching for switch and instanceof 2.4 Loops and Branching Statements
3 Chapter 3: Object-Oriented Design & Class Architecture
3.1 Class Declarations, Constructors, and Object Lifecycle 3.2 Encapsulation and Access Modifiers 3.3 Inheritance, Method Overriding, and Polymorphism 3.4 Abstract Classes and Interfaces 3.5 Nested, Inner, Local, and Anonymous Classes 3.6 Method Overloading, Overload Resolution, and Varargs
4 Chapter 4: Modern Java Data Modeling: Records, Enums, and Sealed Types
4.1 Java Records, Canonical/Compact Constructors, and Component Accessors 4.2 Java Enums: Fields, Methods, and Abstract Constant-Specific Behavior 4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture 4.4 Advanced Record Patterns (JEP 440), Nested Deconstruction, and Sealed Hierarchy Exhaustiveness
5 Chapter 5: Exception Handling and Annotations
5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions 5.2 Exception Handling: try, catch, finally, and Multi-Catch 5.3 Resource Management: try-with-resources and AutoCloseable 5.4 Custom Exceptions and Best Practices 5.5 Java Annotations: Built-in, Meta-Annotations, and Custom Types
6 Chapter 6: Generics and the Collections Framework
6.1 Generics Fundamentals and Generic Methods 6.2 Wildcards, Type Bounds, and Type Erasure 6.3 Core Collections: Lists, Sets, Queues, and Maps 6.4 Sequenced Collections in Java 21 6.5 Sorting, Comparable, and Comparator 6.6 Arrays: Creation, Multidimensional Forms, and the Arrays Utility Class
7 Chapter 7: Functional Programming & Lambda Expressions
7.1 Built-in Functional Interfaces in java.util.function 7.2 Lambda Expressions, Variable Scopes, and Effectively Final 7.3 Method References: Four Varieties and Edge Cases 7.4 Working with java.util.Optional
8 Chapter 8: The Stream API and Data Pipelines
8.1 Stream Creation and Intermediate Operations 8.2 Terminal Operations and Reductions 8.3 Advanced Collectors, Grouping, Partitioning, and Teeing 8.4 Primitive Streams and Parallel Stream Processing
9 Chapter 9: Concurrency, Multithreading, and Virtual Threads
9.1 Thread Fundamentals, Creation, and Lifecycle 9.2 Thread Safety, Synchronization, and the Java Memory Model 9.3 Concurrency Utilities: ExecutorService, Callable, and Future 9.4 Concurrent Collections, Atomics, and Explicit Locks 9.5 Virtual Threads in Java 21 (Carrier Threads, Pinning, and Best Practices)
10 Chapter 10: The Java Platform Module System (JPMS)
10.1 Module Architecture and module-info.java Declarations 10.2 Module Directives: requires, exports, opens, provides, and uses 10.3 Modular Compilation, Packaging, and CLI Execution 10.4 Module Types, Migration Strategies, ServiceLoader SPI, and JLink
11 Chapter 11: Java I/O, NIO.2, and Object Serialization
11.1 Classic Java I/O Streams and Readers/Writers 11.2 Java NIO.2 File API: Path, Paths, and File System Operations 11.3 Advanced NIO.2 Stream Operations and Directory Traversal 11.4 Java Object Serialization and Record Serialization
12 Chapter 12: Implementing Localization and Internationalization
12.1 Locales and Resource Bundles 12.2 Formatting and Parsing Numbers, Currencies, Dates, Times, and Messages
9.1 Thread Fundamentals, Creation, and Lifecycle
Key Takeaways Explain This Topic AI
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.
Last updated: September 2026
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 Thread is generally discouraged in modern application design because Java only permits single class inheritance . Extending Thread prevents 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 . Calling t.sleep(1000) on a thread reference t does not put thread t to 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() , or Thread.join() , the blocking call is aborted, an InterruptedException is thrown , and the interrupt status flag is automatically cleared (reset to false ) !
thread.isInterrupted() (Instance method):
Returns true if the target thread has been interrupted; does not clear or modify the flag .
Thread.interrupted() (Static method):
Tests whether the current thread has been interrupted and clears the interrupt status flag (sets it to false ). // 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 finally blocks 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 calling thread.start() .
Calling setDaemon(true) on a thread that is already running throws IllegalThreadStateException . 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) to Thread.MAX_PRIORITY (10), defaulting to Thread.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's run() 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());
- });