ExecutorService
ExecutorService
Thread Pool Management: It provides thread pooling to manage a set of threads, which is more
efficient than creating a new thread for each task.
Task Submission: It allows you to submit tasks that can be executed asynchronously.
Graceful Shutdown: It supports graceful shutdown of threads, preventing new tasks from being
submitted but allowing already submitted tasks to finish.
Asynchronous Execution: Allows tasks to be executed asynchronously and returns a Future object,
which can be used to retrieve the result of the task.
Automatic Resource Management: Automatically manages threads, reusing threads to execute
tasks, improving system resource usage.
submit(): Submits a task for execution and returns a Future object that can be used to retrieve the
result of the task.
invokeAll(): Submits a collection of tasks for execution and waits for all tasks to complete.
invokeAny(): Submits a collection of tasks for execution and returns the result of the first task that
successfully completes.
shutdown(): Initiates an orderly shutdown of the executor, where previously submitted tasks are
executed, but no new tasks will be accepted.
shutdownNow(): Attempts to stop all actively executing tasks, halts the processing of waiting tasks,
and returns a list of the tasks that were waiting to be executed.
The ExecutorService interface has several implementations provided by the Executors utility class. Some
common ones are:
1) newFixedThreadPool(): Creates a thread pool with a fixed number of threads. It is a good choice
when you have a known number of concurrent tasks.
2) newCachedThreadPool(): Creates a thread pool that creates new threads as needed, but will reuse
previously constructed threads when they are available.
4) newScheduledThreadPool(): Creates a thread pool for scheduling tasks with fixed-rate or fixed-
delay execution policies.
1. Using ExecutorService with Runnable interface: we'll submit Runnable tasks to an ExecutorService.
A Runnable task doesn't return a result but just performs some action.
2. Using ExecutorService with Callable interface: The Callable interface is similar to Runnable but it
can return a result and can throw exceptions. When using Callable, the tasks will return a Future
object, which allows you to retrieve the result once the task is complete.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Key Points:
ExecutorService creates a thread pool, so tasks can be processed concurrently without creating
individual threads for each task.
Future: The submit() method returns a Future object, which represents the result of an
asynchronous computation. You can call get() on a Future to block the main thread and retrieve the
result once the task completes.
shutdown() and shutdownNow() are used to stop the executor and clean up resources.
shutdown() waits for tasks to finish, while shutdownNow() tries to stop all tasks immediately.
Parallel Task Execution: Useful when you need to execute multiple tasks concurrently, such as in
simulations or parallel data processing.
Handling I/O-bound Tasks: For tasks that involve I/O operations (e.g., web scraping, file
processing), ExecutorService can handle multiple I/O tasks concurrently without blocking the main
thread.
Managing Long-Running Tasks: Ideal for managing long-running tasks (e.g., in background
processes) and handling them without blocking the user interface or main thread.
Asynchronous Execution: When you need to execute tasks asynchronously and potentially wait for
their results (using Future).
Scheduled Task Execution: Scenario: Running tasks at fixed intervals, such as data synchronization
or periodic reporting. Example: Using ScheduledExecutorService to perform cleanup tasks every
hour.
1) ThreadPoolExecutor — for executing tasks using a pool of threads. Once a thread is finished
executing the task, it goes back into the pool. If all threads in the pool are busy, then the task has to
wait for its turn.
2) ScheduledThreadPoolExecutor allows to schedule task execution instead of running it immediately
when a thread is available. It can also schedule tasks with fixed rate or fixed delay.
3) ForkJoinPool is a special ExecutorService for dealing with recursive algorithms tasks. If you use a
regular ThreadPoolExecutor for a recursive algorithm, you will quickly find all your threads are busy
waiting for the lower levels of recursion to finish. The ForkJoinPool implements the so-called work-
stealing algorithm that allows it to use available threads more efficiently.
execute(): A method from the Executor interface; it does not return any result or future.
submit(): Returns a Future object, allowing you to get results or exceptions.
shutdown(): Gracefully stops accepting new tasks and allows ongoing tasks to complete.
shutdownNow(): Attempts to stop all ongoing tasks immediately and returns a list of pending tasks.
How do you handle exceptions thrown by tasks executed by the Executor Framework?
Exceptions thrown during task execution can be caught and handled within the run or call methods. If
using Future, you can retrieve the exception by calling get(), which will throw an ExecutionException if
the task failed.
What are some benefits of using a thread pool instead of creating new threads for every task?
Benefits include reduced overhead of thread creation, improved resource utilization, better control over
the maximum number of concurrent tasks, and reduced risk of resource exhaustion.