Study guide
9.3 Concurrency Utilities: ExecutorService, Callable, and Future
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.3 Concurrency Utilities: ExecutorService, Callable, and Future
Key Takeaways Explain This Topic AI
The Executor framework decouples asynchronous task submission from thread management, execution policies, and scheduling through configurable thread pools.
Callable<V> represents a value-returning task that can throw checked exceptions via V call(), whereas Runnable returns void and cannot throw checked exceptions.
Future.get() synchronously blocks until computation completes, unwrapping task exceptions inside an ExecutionException accessible via getCause().
ScheduledExecutorService distinguishes between scheduleAtFixedRate (fixed cadence between task initiations) and scheduleWithFixedDelay (fixed pause between task completion and subsequent start).
In Java SE 21, ExecutorService implements AutoCloseable, allowing try-with-resources blocks to automatically initiate shutdown() and await task termination indefinitely.
Last updated: September 2026
Concurrency Utilities: ExecutorService, Callable, and Future
Manually creating and managing Thread instances for every background task introduces high memory overhead, unpredictable resource consumption, and complicated error handling. The Java Concurrency Utilities ( java.util.concurrent ), introduced in Java 5 and modernised in Java 21, provide high-level abstractions to execute asynchronous tasks via managed thread pools.
- 1. The Executor Framework Hierarchy
- The executor framework is organized as a tier of core interfaces: +-------------------+
- | Executor | (void execute(Runnable))
- +-------------------+
- ^
- |
- +-------------------+
- | ExecutorService | (submit, invokeAll, shutdown)
- +-------------------+
- ^
- |
- +---------------------------+
- | ScheduledExecutorService | (schedule, scheduleAtFixedRate)
- +---------------------------+
- Factory Methods in java.util.concurrent.Executors
Factory Method Underlying Thread Pool Configuration Best Suited For
newSingleThreadExecutor() 1 reusable worker thread with an unbounded LinkedBlockingQueue Strictly sequential task processing
newFixedThreadPool(int nThreads) Fixed pool of N N N threads with an unbounded LinkedBlockingQueue Resource-constrained, predictable steady workloads
newCachedThreadPool() 0 core threads, creates new threads on demand (60s idle keep-alive), SynchronousQueue Bursty, short-lived asynchronous tasks
newScheduledThreadPool(int corePoolSize) Delayed work queue for recurring/scheduled tasks Periodic background jobs, timer tasks
newVirtualThreadPerTaskExecutor() (Java 21) Spawns an ephemeral virtual thread for every submitted task Massive-scale, I/O-bound concurrency
2. Callable<V> vs. Runnable
The Runnable interface has been part of Java since 1.0, while Callable<V> was introduced in Java 5 to support return values and checked exceptions.
- Feature java.lang.Runnable java.util.concurrent.Callable<V>
- Method Signature void run() V call() throws Exception
- Return Value Returns void Returns value of generic type V
- Checked Exceptions Cannot throw checked exceptions Can throw any checked Exception
Executor Submission executor.execute(r) or executor.submit(r) executor.submit(c) // Runnable lambda: No return value, must catch checked exceptions internally
- Runnable runnableTask = () -> {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- System.out.println("Runnable completed");
- };
// Callable lambda: Returns Integer, throws checked Exception directly!
Callable<Integer> callableTask = () -> {
Thread.sleep(500); // Throws InterruptedException directly!
- return 42;
- };
- 3. Working with Future<V>
When a task is submitted to an ExecutorService via submit() , the framework immediately returns a Future<V> representing the pending asynchronous result.
Key Methods of Future<V> :
V get() : Blocks synchronously until computation completes.
Throws InterruptedException : If the calling thread was interrupted while waiting.
Throws ExecutionException : If the background task threw an exception (checked or unchecked). The underlying root cause exception is retrieved via e.getCause() .
Throws CancellationException : If the task was cancelled via cancel() .
V get(long timeout, TimeUnit unit) : Blocks up to the specified timeout. Throws TimeoutException if the computation does not finish within the allotted time.
boolean isDone() : Returns true if the task completed normally, was cancelled, or aborted with an exception.
boolean isCancelled() : Returns true if the task was cancelled before normal completion.
boolean cancel(boolean mayInterruptIfRunning) : Attempts to cancel task execution. If mayInterruptIfRunning is true , the worker thread executing the task is interrupted via Thread.interrupt() . ExecutorService executor = Executors.newSingleThreadExecutor();
- Future<String> future = executor.submit(() -> {
- Thread.sleep(1000);
- if (true) throw new IOException("Disk failure during read");
- return "Data Payload";
- });
- try {
- String result = future.get(); // Blocks until completion
- System.out.println("Result: " + result);
- } catch (InterruptedException e) {
- System.out.println("Caller thread was interrupted!");
- } catch (ExecutionException e) {
- Throwable cause = e.getCause(); // Retrieves java.io.IOException
- System.out.println("Task failed with root cause: " + cause.getMessage());
- } finally {
- executor.shutdown();
- }
- 4. Bulk Task Execution: invokeAll() vs. invokeAny()
ExecutorService provides convenient bulk execution methods:
A. invokeAll()
List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) : Executes all tasks, blocking until all tasks complete (normally or exceptionally).
Returns a List<Future<T>> in the exact same iteration order as the input collection.
B. invokeAny()
T invokeAny(Collection<? extends Callable<T>> tasks) : Executes tasks and blocks until ONE task completes successfully (without throwing an exception).
Returns the result of that fastest successful task and automatically cancels all remaining uncompleted tasks . List<Callable<String>> tasks = List.of(
() -> { Thread.sleep(300); return "Server-A Response"; },
() -> { Thread.sleep(100); return "Server-B Response"; }, // Fastest!
- () -> { Thread.sleep(500); return "Server-C Response"; }
- );
- ExecutorService exec = Executors.newFixedThreadPool(3);
- String fastestResult = exec.invokeAny(tasks);
- System.out.println("Fastest result: " + fastestResult); // "Server-B Response"
- exec.shutdown();
- 5. ScheduledExecutorService Timers and Periodic Tasks
ScheduledExecutorService enables delayed execution and recurring periodic tasks:
Scheduling Methods & Timing Semantics:
schedule(Callable<V> callable, long delay, TimeUnit unit) : One-shot execution after delay .
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) :
Initiates execution at fixed intervals: T 0 , T 0 + period , T 0 + 2 × period , … T_0, T_0 + \text{period}, T_0 + 2\times\text{period}, \dots T 0 , T 0 + period , T 0 + 2 × period , …
If an execution takes longer than period , subsequent executions are delayed to prevent concurrent runs of the same task, but the scheduler catches up afterwards.
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) :
Enforces a fixed delay between the completion of one execution and the initiation of the next .
Next Start Time = (End Time of Previous Execution) + delay . scheduleAtFixedRate (period = 5s, task duration = 2s):
- |-- 2s task --| 3s idle |-- 2s task --| 3s idle |
- 0s 2s 5s 7s 10s
scheduleWithFixedDelay (delay = 5s, task duration = 2s):
- |-- 2s task --|------ 5s delay ------|-- 2s task --|------ 5s delay ------|
- 0s 2s 7s 9s 14s
Warning
If any periodic execution throws an unhandled runtime exception or error, all subsequent executions of the scheduled task are permanently suppressed and terminated without notifying the scheduler or crashing the pool!
6. Asynchronous Pipelines with CompletableFuture
Java 8 introduced CompletableFuture<T> (implementing Future<T> and CompletionStage<T> ) to build non-blocking asynchronous processing pipelines: CompletableFuture.supplyAsync(() -> fetchUserData(userId), executor)
- .thenApply(user -> user.getEmail()) // Transform result
- .thenAccept(email -> sendNotification(email)) // Consume result
- .exceptionally(ex -> {
- System.err.println("Pipeline failed: " + ex.getMessage());
- return null;
- });
supplyAsync(Supplier<U>) : Initiates asynchronous task returning a value.
runAsync(Runnable) : Initiates asynchronous task returning Void .
thenApply(Function) : Maps/transforms the completed value.
thenAccept(Consumer) : Consumes the completed value without returning a result.
thenCombine(otherFuture, BiFunction) : Combines results of two independent futures.
join() vs. get() : join() does not throw checked exceptions (it throws unchecked CompletionException ), making it ideal for lambda pipelines.
7. Lifecycle, Graceful Shutdown, and AutoCloseable
An ExecutorService maintains active non-daemon OS worker threads that will keep the JVM process alive indefinitely if not properly shut down.
The Canonical Graceful Shutdown Protocol: ExecutorService executor = Executors.newFixedThreadPool(4);
// Submit tasks...
- executor.shutdown(); // 1. Stop accepting new tasks; allow active/queued tasks to finish
- try {
- // 2. Wait up to 5 seconds for existing tasks to complete
- if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
- List<Runnable> droppedTasks = executor.shutdownNow(); // 3. Cancel active tasks via interrupt
- System.out.println("Dropped tasks count: " + droppedTasks.size());
- }
- } catch (InterruptedException e) {
- executor.shutdownNow();
- Thread.currentThread().interrupt();
- }
shutdown() : Rejects new tasks ( RejectedExecutionException ), but completes all previously submitted and queued tasks.
shutdownNow() : Attempts to cancel active tasks via Thread.interrupt() , drains the queue, and returns List<Runnable> of unstarted tasks.
isShutdown() : Returns true once shutdown() or shutdownNow() has been called.
isTerminated() : Returns true only after all tasks have finished following shutdown.
Java 19+ / Java 21 AutoCloseable Integration
In modern Java, ExecutorService extends AutoCloseable . Using ExecutorService inside a try-with-resources statement automatically invokes close() , which calls shutdown() and blocks in awaitTermination() indefinitely until all tasks complete: try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) {
- exec.submit(() -> System.out.println("Task 1 processing"));
- exec.submit(() -> System.out.println("Task 2 processing"));
} // Auto-closes: invokes shutdown() and waits for all tasks to terminate!
// Reaching this line guarantees that all submitted tasks are fully finished!