Script Valley
Java: Complete Language Course
Concurrency and MultithreadingLesson 5.3

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.

Up next

Java atomic classes and concurrent collections

Sign in to track progress