Detailed explanation
The Executor framework in java.util.concurrent separates task submission from the mechanics of execution. Instead of creating a new Thread for every operation, application code submits a Runnable or Callable to an executor, which decides when and where it runs. This separation centralises concurrency policies and allows worker threads to be reused, reducing repeated thread-creation overhead. It matters in applications that process independent requests or background jobs, where uncontrolled thread creation can exhaust resources rather than improve throughput.
Executor is the basic interface, with one method: execute(Runnable). It does not itself guarantee asynchronous execution or a thread pool; those behaviours depend on the implementation. ExecutorService extends Executor with lifecycle management and methods such as submit, invokeAll, and invokeAny. Unlike Runnable, Callable can return a value and throw checked exceptions. Calling submit returns a Future, which represents the task’s eventual completion and provides result retrieval, cancellation, and status checks. ScheduledExecutorService additionally supports delayed and periodic execution.
For example, Executors.newFixedThreadPool(4) creates an ExecutorService with at most four active worker threads and an unbounded task queue. With Future<Integer> future = pool.submit(() -> 2 + 2), a worker executes the Callable, and future.get() returns 4, blocking if necessary. If the task fails, get() throws ExecutionException wrapping the cause. Under the hood, ThreadPoolExecutor coordinates workers, a work queue, pool-size limits, idle-thread retention, and a rejection policy. Its submission logic generally creates core workers first, then queues tasks, and creates additional workers up to the maximum only when queueing fails. An unbounded queue can therefore accumulate work and consume excessive memory.
Lifecycle management is part of a complete answer. shutdown() stops accepting new tasks while allowing previously submitted work to finish; it does not wait for completion. Use awaitTermination when waiting is required. shutdownNow() attempts to interrupt running tasks and returns tasks that never started, but cannot forcibly stop code that ignores interruption. Interviewers usually look for the distinction between Executor and ExecutorService, sensible pool and queue choices, correct Future exception handling, and reliable cleanup rather than merely remembering a factory method.
Key points to remember
- Executor decouples submission from execution through execute(Runnable).
- ExecutorService adds task submission with Futures, bulk operations, and lifecycle management.
- Runnable has no result; Callable returns a result and can throw checked exceptions.
- ThreadPoolExecutor exposes worker limits, queue configuration, and rejection policies.
- Shut down owned executors and handle blocking, exceptions, and interruption deliberately.
Common mistakes candidates make
- Assuming every Executor uses a thread pool or always executes tasks asynchronously.
- Treating a fixed thread count as a bound on queued work; newFixedThreadPool uses an unbounded queue.
- Assuming shutdown waits for completion or shutdownNow guarantees that running tasks stop.
Likely follow-up questions
What is the difference between execute() and submit()?
execute() accepts a Runnable and returns nothing, whereas submit() accepts Runnable or Callable tasks and returns a Future. With submit(), task failures are captured and reported by Future.get() as ExecutionException; with ThreadPoolExecutor.execute(), an uncaught task exception normally reaches the worker thread’s uncaught-exception handler.
How would you configure a thread pool for production?
Choose worker counts based on CPU capacity, blocking behaviour, and downstream resource limits, then validate under load. A ThreadPoolExecutor with a bounded queue and an explicit rejection policy makes overload handling deliberate rather than allowing unchecked queue growth.
Does Future.cancel(true) immediately stop a running task?
No; it requests interruption if the task is running, so cancellation relies on the task cooperating with interruption. Code should respond to interruptible operations or check interruption status, and should not silently swallow InterruptedException.
Interview tip: Start with task–execution separation, walk through the four-thread Future example, and finish with queue limits and shutdown semantics.
