Executor Framework in Java
The Executor framework, in the java.util.concurrent package, is used to run tasks on a pool of reusable threads instead of creating a new Thread for every task. You hand a task (a Runnable or Callable) to an executor, and it decides which thread runs it and when. A thread pool keeps a set of worker threads alive and gives each new task to a free worker, which saves the cost of creating threads and limits how many run at once. In this lesson, you will learn the main interfaces and how to submit tasks.
Why Use a Thread Pool?
| Creating threads by hand | Using an executor |
|---|---|
| A new thread for every task (slow to create) | Threads are created once and reused |
| No limit: 10,000 tasks can mean 10,000 threads | A fixed number of workers; extra tasks wait in a queue |
| You manage starting, joining and errors | The executor manages threads; you get Future results |
| Hard to shut down cleanly | shutdown() and awaitTermination() |
The Main Types
| Type | Description |
|---|---|
Executor | It is used to run a Runnable: one method, execute(Runnable). |
ExecutorService | It extends Executor with submit, invokeAll, invokeAny, shutdown and more. |
ScheduledExecutorService | It runs tasks after a delay or repeatedly. |
Executors | A utility class with factory methods such as newFixedThreadPool(n). |
Future<V> | It represents the result of a submitted task. |
Methods of ExecutorService
| Method | Description |
|---|---|
execute(Runnable task) | It runs a task with no result. |
submit(Runnable or Callable) | It runs a task and returns a Future. |
invokeAll(tasks) | It runs all tasks and waits for all results. |
invokeAny(tasks) | It returns the result of the first task to succeed. |
shutdown() | It stops accepting new tasks; queued tasks still run. |
awaitTermination(time, unit) | It waits until all tasks finish or the time runs out. |
close() | It shuts down and waits (Java 19; used with try-with-resources). |
Example: Six Tasks on Three Threads
Let's see an example that runs six tasks on a pool of three worker threads.
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolDemo {
public static void main(String[] args) {
Set<String> workers = ConcurrentHashMap.newKeySet();
long start = System.nanoTime();
try (ExecutorService pool = Executors.newFixedThreadPool(3)) {
for (int i = 1; i <= 6; i++) {
int task = i;
pool.submit(() -> {
workers.add(Thread.currentThread().getName());
sleep(200);
return task;
});
}
}
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("Different worker threads used: " + workers.size());
System.out.println("Took about two rounds (400-600 ms)? "
+ (ms >= 400 && ms < 600));
}
static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}Different worker threads used: 3
Took about two rounds (400-600 ms)? trueExplanation: Executors.newFixedThreadPool(3) creates a pool with three worker threads. Six tasks are submitted; three run at once and the other three wait in the queue, so the work takes two rounds of 200 milliseconds. Only three threads were ever created, and each was reused. Since Java 19, ExecutorService is AutoCloseable, so try-with-resources shuts the pool down and waits for the tasks to finish.
Sizing a Pool
Common Mistakes
Forgetting to shut down the executor. Its non-daemon threads keep the program alive.
Creating a new executor for every task. Create one and reuse it.
Ignoring the Future from submit. An exception thrown by the task is hidden inside it until you call get().
Interview Corner
What is the Executor framework, and why use it?
The Executor framework in java.util.concurrent separates task submission from task execution. You submit Runnable or Callable tasks to an ExecutorService, typically created by the Executors factory methods, and it runs them on a managed pool of reusable threads. Thread pools avoid the cost of creating threads for every task, bound the number of concurrent threads, queue excess work, return results and exceptions through Future, and support orderly shutdown.
Practice
- Easy: Submit five tasks that each print their task number and thread name to a pool of two threads.
- Harder: Use
invokeAllto compute the squares of the numbers 1 to 10 in a pool of four threads, and print the sum of the results.
Key Takeaways
Was this tutorial helpful?