ExecutorService, Future & CompletableFuture
After understanding threads and multithreading basics, the next step in modern Java concurrency is learning how to manage threads efficiently. Manually creating threads is inefficient, difficult to control, and not scalable. Java solves these issues using ExecutorService, Future, and CompletableFuture.
Why ExecutorService?
ExecutorService provides a flexible framework for managing and reusing threads. It removes the need to manually create, start, and manage individual threads.
- Efficient resource utilization
- Thread reuse using thread pools
- Better performance
- Simpler asynchronous programming
- Avoids creating too many threads
Types of Executor Services
Java offers several ready-to-use executors based on usage:
- newSingleThreadExecutor: Executes tasks sequentially in one thread.
- newFixedThreadPool: Maintains a fixed number of threads.
- newCachedThreadPool: Creates threads as needed; good for short-lived tasks.
- newScheduledThreadPool: Executes delayed and periodic tasks.
Submitting Tasks
ExecutorService lets you submit tasks instead of manually starting threads. You can submit both Runnable and Callable tasks using the submit() method.
Future – Handling Asynchronous Results
Future represents the output of an asynchronous task. When you submit a Callable, the method returns a Future object that you can use to:
- Check if a task is completed
- Cancel the task
- Retrieve results using get()
Future helps manage long-running tasks without blocking the main thread.
Limitations of Future
Although useful, Future has limitations:
- No callback mechanism
- No chaining of tasks
- get() blocks the thread
To overcome these, Java introduced CompletableFuture.
CompletableFuture – Modern Asynchronous Programming
CompletableFuture is a powerful API for building non-blocking, asynchronous, and event-driven systems. It supports chaining, combining multiple tasks, and writing clean concurrent code.
Main Features of CompletableFuture
- runAsync – executes a task asynchronously
- supplyAsync – executes and returns a result
- thenApply – transforms the result
- thenAccept – consumes the result
- thenRun – runs a new task after completion
- allOf – waits for multiple tasks
- anyOf – returns the first completed result
CompletableFuture supports both non-blocking (callbacks) and blocking (get) operations, making it suitable for modern reactive systems.
Why Use ExecutorService + CompletableFuture?
Combining thread pools with CompletableFuture allows efficient and scalable async programming:
- Reduces context switching
- Improves performance
- Keeps CPU busy without creating new threads
- Allows structured concurrency
Shutting Down Executors
Always shut down the executor after use:
- shutdown() – finishes ongoing tasks
- shutdownNow() – stops tasks immediately
Not shutting down executors can lead to memory leaks and thread exhaustion.
Common Use Cases
- Asynchronous APIs
- Parallel computation
- Microservices communication
- Web request processing
- Batch jobs
ExecutorService, Future, and CompletableFuture form the core of modern Java concurrency, enabling scalable and high-performance applications.
Comments
Post a Comment