ExecutorService / Executors.newFixedThreadPool()
| Since: | Java 5(2004) |
|---|
A mechanism for efficiently executing multiple tasks in parallel using a thread pool (available since Java 5). Because it automatically manages thread creation, reuse, and termination, it is safer and more performant than using Thread directly.
Syntax
import java.util.concurrent.*; // Creates a fixed-size thread pool. ExecutorService exec = Executors.newFixedThreadPool(numThreads); // Creates an Executor backed by a single thread. ExecutorService exec = Executors.newSingleThreadExecutor(); // Creates a pool that grows and shrinks as needed. ExecutorService exec = Executors.newCachedThreadPool(); // Submits a Runnable task (no return value). exec.execute(Runnable task); // Submits a Callable task and returns a Future (with return value). Future<T> future = exec.submit(Callable<T> task); T result = future.get(); // Blocks until the result is available. // Shuts down the ExecutorService (required). exec.shutdown(); exec.shutdownNow(); // Forcibly stops all running tasks.
Main Methods
| Method | Description |
|---|---|
| Executors.newFixedThreadPool(n) | Creates a fixed-size pool with n threads. |
| Executors.newSingleThreadExecutor() | Queues and executes tasks sequentially on a single thread. |
| Executors.newCachedThreadPool() | Creates and reuses threads as needed. Well-suited for short-lived tasks. |
| execute(Runnable) | Executes a task asynchronously. Returns no value. |
| submit(Callable<T>) | Executes a task asynchronously and returns the result as a Future<T>. |
| future.get() | Blocks and waits until the task's result is available. |
| shutdown() | Stops accepting new tasks and waits for existing tasks to complete before terminating. |
| shutdownNow() | Interrupts running tasks and shuts down immediately. |
| awaitTermination(timeout, unit) | Waits for termination up to the specified timeout. |
Sample Code
ExecutorServiceExample.java
import java.util.concurrent.*;
class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException {
// Runs 5 tasks concurrently using a pool of 3 threads.
ExecutorService exec = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
final int taskId = i;
exec.execute(() -> {
System.out.println("Task " + taskId + " running on: " + Thread.currentThread().getName());
try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("Task " + taskId + " completed.");
});
}
// Stops accepting new tasks and waits for remaining tasks to finish.
exec.shutdown();
// Uses submit() to run tasks that return a value.
ExecutorService exec2 = Executors.newFixedThreadPool(2);
Future<Integer> future1 = exec2.submit(() -> { Thread.sleep(200); return 10 + 20; });
Future<Integer> future2 = exec2.submit(() -> { Thread.sleep(100); return 30 + 40; });
try {
System.out.println("Result 1: " + future1.get());
System.out.println("Result 2: " + future2.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
exec2.shutdown();
}
}
}
The command looks like this:
javac ExecutorServiceExample.java java ExecutorServiceExample # * Thread execution order may vary by environment Task 1 running on: pool-1-thread-1 Task 2 running on: pool-1-thread-2 Task 3 running on: pool-1-thread-3 Task 1 completed. Task 4 running on: pool-1-thread-1 Task 2 completed. Task 5 running on: pool-1-thread-2 Task 3 completed. Task 4 completed. Task 5 completed. Result 1: 30 Result 2: 70
Note: Thread execution order may vary depending on the runtime environment.
Common Mistakes
Common Mistake 1: Forgetting to call shutdown() leaves the program running indefinitely
Son Goku submitted tasks but forgot to call shutdown(), so the thread pool remained alive and the program never terminated.
ExecutorNoShutdownNg.java
import java.util.concurrent.*;
class ExecutorNoShutdownNg {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(() -> System.out.println("Task completed."));
}
}
The command looks like this:
javac ExecutorNoShutdownNg.java java ExecutorNoShutdownNg Task completed. (the program hangs and never exits)
As Vegeta fixed it, calling shutdown() inside a try-finally block ensures the program exits normally.
ExecutorNoShutdownOk.java
import java.util.concurrent.*;
class ExecutorNoShutdownOk {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
try {
exec.execute(() -> System.out.println("Task completed."));
} finally {
exec.shutdown();
}
}
}
The command looks like this:
javac ExecutorNoShutdownOk.java java ExecutorNoShutdownOk Task completed.
Common Mistake 2: Exceptions thrown by submit() tasks go unnoticed until Future.get() is called
Piccolo submitted a task using submit() but never called Future.get(), so the exception thrown inside the task was silently swallowed.
ExecutorExceptionNg.java
import java.util.concurrent.*;
class ExecutorExceptionNg {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
Future> future = exec.submit(() -> {
throw new RuntimeException("Something went wrong!");
});
exec.shutdown();
System.out.println("Done.");
}
}
The command looks like this:
javac ExecutorExceptionNg.java java ExecutorExceptionNg Done.
As Son Gohan fixed it, calling Future.get() and catching ExecutionException lets you detect any exception thrown inside the task.
ExecutorExceptionOk.java
import java.util.concurrent.*;
class ExecutorExceptionOk {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
Future> future = exec.submit(() -> {
throw new RuntimeException("Something went wrong!");
});
try {
future.get();
} catch (ExecutionException e) {
System.out.println("Task threw an exception: " + e.getCause().getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
exec.shutdown();
}
}
}
The command looks like this:
javac ExecutorExceptionOk.java java ExecutorExceptionOk Task threw an exception: Something went wrong!
Notes
Using a thread pool reduces the overhead of creating threads and improves application throughput. For Executors.newFixedThreadPool(), it is common to set the thread count to match the number of CPU cores. For I/O-bound workloads (where I/O operations such as file access or network communication account for most of the processing time), using more threads than CPU cores can be more effective.
If you do not call shutdown(), the program may hang and never terminate. Always call it using a try-finally block. For more advanced async processing, use CompletableFuture, which lets you compose and chain multiple asynchronous tasks.
For the basics of threads, see new Thread() / thread.start() / Runnable.
If you find any errors or copyright issues, please contact us.