Java ExecutorService and thread pool basics
ExecutorService, Executors factory, thread pool, fixed pool, cached pool, submit, Future, Callable, shutdown, awaitTermination, task queue
ExecutorService and Thread Pools
Creating a new Thread per task is expensive — thread creation takes milliseconds and each thread consumes significant memory. Thread pools reuse a fixed set of worker threads across many tasks.
Fixed Thread Pool
import java.util.concurrent.*;
ExecutorService pool = Executors.newFixedThreadPool(4); // 4 worker threads
for (int i = 0; i < 20; i++) {
final int taskId = i;
pool.submit(() -> {
System.out.println("Task " + taskId + " on " + Thread.currentThread().getName());
});
}
pool.shutdown(); // no new tasks accepted
pool.awaitTermination(10, TimeUnit.SECONDS); // wait for running tasks to complete
Callable and Future — Tasks that Return Values
Callable task = () -> {
Thread.sleep(100);
return 42;
};
Future future = pool.submit(task);
// Continue other work here...
int result = future.get(); // blocks until the task completes
System.out.println("Result: " + result); // 42
Runnable has no return value and cannot throw checked exceptions. Callable<T> returns a value and can throw. Future.get() throws ExecutionException wrapping the task's exception — unwrap with getCause().
Always call shutdown() on an executor when done. A pool that is never shut down keeps non-daemon worker threads alive, preventing the JVM from exiting even after main() returns.
Choose the right pool type: newFixedThreadPool(n) for CPU-bound tasks (set n to available cores), newCachedThreadPool() for many short-lived I/O tasks, and newSingleThreadExecutor() when tasks must execute sequentially.
