The ExecutorService is part of the java.util.concurrent package in Java and provides a powerful framework for managing threads. It simplifies concurrent programming by abstracting the creation, management, and termination of threads.
Key Features of ExecutorService
- Thread Pool Management: Manages a pool of threads, reusing them to execute tasks, reducing the overhead of thread creation.
- Task Submission: Allows for both one-shot and periodic task submissions.
- Graceful Shutdown: Provides mechanisms to shut down the executor gracefully or forcefully.
- Future and Callable Support: Allows task execution with results via
CallableandFuture.
Common Methods in ExecutorService
1. execute(Runnable command)
Executes a Runnable task asynchronously.
Example:
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Task is running..."));
executor.shutdown();
2. submit(Callable<T> task) or submit(Runnable task)
Submits a task for execution and returns a Future object. With Callable, you can get a result or handle exceptions.
Example:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
try {
System.out.println("Result: " + future.get()); // Waits for the task to complete
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
3. invokeAll(Collection<? extends Callable<T>> tasks)
Executes a collection of Callable tasks and returns a list of Future objects. Blocks until all tasks are complete.
Example:
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<Integer>> tasks = Arrays.asList(
() -> 1,
() -> 2,
() -> 3
);
try {
List<Future<Integer>> results = executor.invokeAll(tasks);
for (Future<Integer> result : results) {
System.out.println("Result: " + result.get());
}
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
4. invokeAny(Collection<? extends Callable<T>> tasks)
Executes a collection of Callable tasks and returns the result of the first completed task. Other tasks may be canceled.
Example:
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<Integer>> tasks = Arrays.asList(
() -> {
Thread.sleep(200);
return 1;
},
() -> {
Thread.sleep(100);
return 2;
},
() -> 3
);
try {
Integer result = executor.invokeAny(tasks);
System.out.println("First completed result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
5. shutdown() and shutdownNow()
shutdown(): Prevents new tasks from being submitted while allowing previously submitted tasks to complete.shutdownNow(): Attempts to stop all actively executing tasks and halts the processing of waiting tasks.
Example:
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Task 1 running..."));
executor.execute(() -> System.out.println("Task 2 running..."));
executor.shutdown();
if (executor.isShutdown()) {
System.out.println("Executor is shut down.");
}
6. awaitTermination(long timeout, TimeUnit unit)
Blocks until all tasks have completed execution after a shutdown request or the timeout occurs.
Example:
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Task 1 running..."));
executor.shutdown();
try {
if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("All tasks completed.");
} else {
System.out.println("Timeout occurred before completion.");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Types of Executors
1. newSingleThreadExecutor()
Creates an executor with a single thread.
Use Case: Sequential task execution.
2. newFixedThreadPool(int nThreads)
Creates an executor with a fixed number of threads.
Use Case: Fixed concurrency level.
3. newCachedThreadPool()
Creates an executor with a dynamic thread pool that grows as needed.
Use Case: Short-lived, asynchronous tasks.
4. newScheduledThreadPool(int corePoolSize)
Creates a thread pool that can schedule tasks.
Example:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> System.out.println("Scheduled task executed!"), 3, TimeUnit.SECONDS);
scheduler.shutdown();
Best Practices
- Always call
shutdown()orshutdownNow()to release resources. - Use
Futureto handle exceptions and get results. - Use appropriate executor types based on the workload.
- Avoid using raw threads; prefer
ExecutorServicefor better scalability and management. - Use
awaitTermination()to ensure tasks complete before proceeding.
Summary
The ExecutorService is a robust and versatile tool in Java’s concurrency framework. It abstracts thread management, improves efficiency, and simplifies the execution of concurrent tasks. Understanding its methods and use cases will help you write cleaner and more efficient multi-threaded code.