Syntax
ExecutorService executor = Executors.newFixedThreadPool(n);Examples
Basic ExecutorService Usage
Submitting tasks to a thread pool instead of manually managing Thread objects.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Running task " + taskId + " on " + Thread.currentThread().getName());
});
}
executor.shutdown(); // stop accepting new tasks, let submitted ones finishGetting Results with Future
submit() returns a Future, letting you retrieve a task's result once it completes.
import java.util.concurrent.*;
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(100);
return 42;
});
try {
Integer result = future.get(); // blocks until the task finishes
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
System.out.println("Task failed: " + e.getMessage());
}
executor.shutdown();Different Executor Types
Executors provides several pool configurations for different needs.
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
ExecutorService fixedPool = Executors.newFixedThreadPool(4); // exactly 4 threads
ExecutorService cachedPool = Executors.newCachedThreadPool(); // grows/shrinks as needed
ExecutorService singleThread = Executors.newSingleThreadExecutor(); // one thread, tasks run in order
singleThread.submit(() -> System.out.println("Task A"));
singleThread.submit(() -> System.out.println("Task B")); // guaranteed to run after A
fixedPool.shutdown();
cachedPool.shutdown();
singleThread.shutdown();Best practices
- Always call shutdown() (or shutdownNow() for immediate termination) when done with an ExecutorService - forgetting to do so leaves threads running indefinitely
- Use newFixedThreadPool() for a predictable, bounded number of concurrent tasks, protecting your system from unbounded thread creation
- Handle both InterruptedException and ExecutionException when calling future.get(), since either can occur if the task fails or is interrupted
- Prefer ExecutorService over manually creating raw Thread objects for any real application - it is more efficient and easier to manage correctly
At a glance
- Purpose
- General-purpose application development
- File extension
- .java
- Runs in
- Java Virtual Machine
- Usually used with
- JDK and Java libraries
Specifications & further reading
Related Java documentation
Creating Threads (Thread & Runnable)
A thread is an independent path of execution within a program, letting multiple tasks run seemingly simultaneously. Java offers two main ways to create a thread: extending the Thread class directly and overriding its run() method, or implementing the Runnable interface and passing it to a Thread - the Runnable approach is generally preferred, since Java doesn't support multiple inheritance and Runnable keeps your class free to extend something else.synchronized Keyword
The synchronized keyword prevents multiple threads from executing a block of code (or a method) at the same time on the same object, protecting shared, mutable data from race conditions. When one thread enters a synchronized block, it acquires a lock on the specified object; any other thread trying to enter a synchronized block on that same object must wait until the lock is released.Atomic Variables
Classes like AtomicInteger, AtomicLong, and AtomicBoolean, from java.util.concurrent.atomic, provide thread-safe operations on single variables without needing explicit synchronized blocks. They use low-level, lock-free CPU instructions (compare-and-swap) to guarantee that operations like incrementAndGet() are atomic - performed as a single, uninterruptible step - even when many threads use them concurrently.Thread Control: sleep, join, interrupt
Java provides several methods for coordinating and controlling thread execution. Thread.sleep() pauses the current thread for a specified duration. join() makes the calling thread wait until another thread finishes before continuing. interrupt() signals a thread that it should stop what it's doing, a cooperative mechanism the target thread must actively check for and respond to.
A thread is an independent path of execution within a program, letting multiple tasks run seemingly simultaneously. Java offers two main ways to create a thread: extending the Thread class directly and overriding its run() method, or implementing the Runnable interface and passing it to a Thread - the Runnable approach is generally preferred, since Java doesn't support multiple inheritance and Runnable keeps your class free to extend something else.synchronized Keyword
The synchronized keyword prevents multiple threads from executing a block of code (or a method) at the same time on the same object, protecting shared, mutable data from race conditions. When one thread enters a synchronized block, it acquires a lock on the specified object; any other thread trying to enter a synchronized block on that same object must wait until the lock is released.Atomic Variables
Classes like AtomicInteger, AtomicLong, and AtomicBoolean, from java.util.concurrent.atomic, provide thread-safe operations on single variables without needing explicit synchronized blocks. They use low-level, lock-free CPU instructions (compare-and-swap) to guarantee that operations like incrementAndGet() are atomic - performed as a single, uninterruptible step - even when many threads use them concurrently.Thread Control: sleep, join, interrupt
Java provides several methods for coordinating and controlling thread execution. Thread.sleep() pauses the current thread for a specified duration. join() makes the calling thread wait until another thread finishes before continuing. interrupt() signals a thread that it should stop what it's doing, a cooperative mechanism the target thread must actively check for and respond to.