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

Popular posts from this blog

Spring Boot on AWS EC2: Upload to S3 Securely Using IAM Role

Java Streams Intermediate Operations Explained with Examples

ConcurrentHashMap vs Synchronized HashMap in Java