skip to content

What are the main factory methods on java.util.concurrent.Executors, and what kind of thread pool does each create?

level: juniorimportance: must knowfreq 70%

answer

  1. Fixed = n threads + unbounded queue
  2. Cached = unbounded threads + SynchronousQueue
  3. Single = 1 thread, ordered
  4. Scheduled = delayed/periodic
  5. WorkStealing (Java 8) / VirtualThreadPerTask (Java 21)

basics

~10 s

Executors is a helper class with static methods that build ready-made thread pools: newFixedThreadPool (fixed number of threads), newCachedThreadPool (grows/shrinks on demand), newSingleThreadExecutor (one thread), and newScheduledThreadPool (for delayed/repeating tasks).

solid answer

~40 s

Executors is a utility class of static factory methods that return pre-configured ExecutorService instances. newFixedThreadPool(n) keeps exactly n threads with an unbounded queue. newCachedThreadPool() creates threads on demand and reuses idle ones (60s keep-alive), with no fixed ceiling. newSingleThreadExecutor() runs tasks sequentially on one thread, guaranteeing order. newScheduledThreadPool(n)/newSingleThreadScheduledExecutor() support delayed and periodic tasks. Java 8 added newWorkStealingPool() (a ForkJoinPool sized to the CPU count, good for many small independent tasks). Java 21 added newVirtualThreadPerTaskExecutor() (one lightweight virtual thread per task). Each is a thin wrapper that picks sensible defaults around ThreadPoolExecutor (or ForkJoinPool), so you submit Runnable/Callable and don't manage threads yourself.

code

java · 10 lines
java
ExecutorService fixed     = Executors.newFixedThreadPool(4);
ExecutorService cached    = Executors.newCachedThreadPool();
ExecutorService single    = Executors.newSingleThreadExecutor();
ScheduledExecutorService sched = Executors.newScheduledThreadPool(2);
ExecutorService stealing  = Executors.newWorkStealingPool();          // Java 8+
ExecutorService virtual   = Executors.newVirtualThreadPerTaskExecutor(); // Java 21+

Future<Integer> f = fixed.submit(() -> 6 * 7);
System.out.println(f.get());  // 42
fixed.shutdown();

go deeper

for a junior

Name the four classic factories and one-line what each does (fixed count, elastic, single ordered, scheduled). Know you submit Runnable/Callable and get a Future.

for a middle

Add the underlying queue/thread-count for each, the Java 8 work-stealing pool, the Java 21 virtual-thread-per-task executor, and remember to shut pools down.

for a senior

Explain each factory in terms of the ThreadPoolExecutor parameters it sets (core/max/queue/keep-alive) and articulate which workload each suits (CPU vs I/O bound, ordered vs unordered).

for a principal

Frame factory choice as a capacity/back-pressure and failure-isolation decision; know when to bypass the factory entirely for an explicitly configured pool, and the migration story toward virtual threads.

## What problem this solves A **thread** is an independent path of execution the operating system schedules. Creating a thread is relatively expensive (memory for its stack, OS bookkeeping), so creating one per task and throwing it away does not scale. A **thread pool** keeps a set of worker threads alive and feeds them a stream of tasks, amortizing the creation cost and bounding how much work runs at once. In Java the abstraction for "submit work, let a pool run it" is **`ExecutorService`** (an interface). You hand it a **`Runnable`** (a task returning nothing) or a **`Callable<T>`** (a task returning a value), and `submit(...)` gives you back a **`Future<T>`** — a handle you can later call `.get()` on to wait for and retrieve the result. ## What `Executors` is `java.util.concurrent.Executors` is a **utility class** (only static methods) of **factory methods** — methods whose job is to construct and return a ready-made object. Each method returns an `ExecutorService` (or the scheduling sub-interface `ScheduledExecutorService`) already configured with a particular strategy, so you don't have to assemble the lower-level `ThreadPoolExecutor` yourself. ## The factory methods - **`newFixedThreadPool(int n)`** — a pool of exactly `n` worker threads. If more tasks arrive than there are free threads, the extras wait in a queue. The queue here is an **unbounded `LinkedBlockingQueue`** (it can hold an effectively unlimited number of waiting tasks). - **`newCachedThreadPool()`** — starts with zero threads and **creates a new thread for every task that arrives when no idle thread is free**. Idle threads are kept for 60 seconds and then discarded. There is no fixed upper limit on thread count (the nominal max is `Integer.MAX_VALUE`). It uses a `SynchronousQueue`, a queue with **zero capacity** — a task can only be handed off if a thread is ready to take it right now, otherwise a new thread is spun up. - **`newSingleThreadExecutor()`** — exactly one worker thread with an unbounded queue. Because there is only one thread, tasks run **one at a time, in submission order** — useful when you need serialization (e.g., writing to a file). It is also wrapped so the thread count can't be reconfigured. - **`newScheduledThreadPool(int n)`** and **`newSingleThreadScheduledExecutor()`** — return a `ScheduledExecutorService`, which adds `schedule(...)` (run once after a delay), `scheduleAtFixedRate(...)`, and `scheduleWithFixedDelay(...)` (run repeatedly). - **`newWorkStealingPool()`** (Java 8) — returns a `ForkJoinPool` sized by default to the number of available processors, where idle threads "steal" queued tasks from busy threads' deques. Good for a large number of small, independent tasks; does **not** guarantee execution order. - **`newVirtualThreadPerTaskExecutor()`** (Java 21) — creates **one virtual thread per task**. A **virtual thread** is a lightweight thread managed by the JVM (not 1:1 with an OS thread), so you can have millions of them cheaply. There is no pooling of platform threads here — every task gets its own virtual thread that simply ends when the task finishes. Ideal for I/O-bound work with very high concurrency. ## How to use one ```java ExecutorService pool = Executors.newFixedThreadPool(4); Future<Integer> f = pool.submit(() -> 2 + 2); int result = f.get(); // 4 pool.shutdown(); // stop accepting tasks; let running ones finish ``` You call `shutdown()` (graceful) or `shutdownNow()` (attempt to cancel) when done, otherwise the non-daemon worker threads keep the JVM alive. ## Why this matters Knowing which factory maps to which behaviour — fixed parallelism, elastic threads, single-threaded ordering, scheduling, work-stealing, or virtual-thread-per-task — lets you pick the right tool. Crucially, the convenient ones (`newFixedThreadPool`, `newCachedThreadPool`) hide *unbounded* resources, which is the central hazard covered by the follow-on questions.

  • What is the difference between submit() and execute()?
    execute(Runnable) is from the base Executor interface and returns nothing — an uncaught exception propagates to the thread's uncaught-exception handler. submit(...) accepts Runnable or Callable, returns a Future, and captures any thrown exception inside that Future (re-thrown wrapped in ExecutionException on get()).
  • Why do you need to call shutdown()?
    Pool worker threads are non-daemon by default, so they keep the JVM running even after main() finishes. shutdown() stops accepting new tasks and lets queued ones complete; shutdownNow() additionally interrupts running tasks and returns the queued ones.

Think of a restaurant kitchen: a fixed pool is a kitchen with exactly 4 cooks and an order spike just stacks tickets endlessly; a cached pool hires a new cook the instant every order arrives and lays them off when idle; a single-thread pool is one cook doing orders strictly in sequence.

saying these in an interview costs you the question

  • Saying newFixedThreadPool has a bounded queue (its queue is unbounded — that's the OOM risk)
  • Claiming newCachedThreadPool has a fixed maximum thread count
  • Confusing newScheduledThreadPool with a Timer (the Executors version is multi-threaded and resilient to task exceptions)
  • Thinking newWorkStealingPool guarantees task ordering

context