What are the constructor parameters of a ThreadPoolExecutor, and what does each one control?
answer
- core / max / keep-alive / queue / factory / handler
- core = kept alive, max = ceiling
- keep-alive shrinks threads above core
- ThreadFactory = names + daemon + priority
- RejectedExecutionHandler = overload policy
basics
~20 sA ThreadPoolExecutor reuses a set of worker threads to run tasks. Its main settings are: core pool size (threads kept alive), maximum pool size (most threads allowed), keep-alive time (how long extra threads idle before stopping), and a work queue that holds waiting tasks.
solid answer
~40 sThreadPoolExecutor's full constructor takes six things: corePoolSize (number of threads kept alive even when idle), maximumPoolSize (upper bound on worker threads), keepAliveTime plus its TimeUnit (how long threads above the core size may sit idle before being terminated), a BlockingQueue<Runnable> workQueue (holds tasks waiting for a free thread), a ThreadFactory (creates new threads, letting you name them, set daemon status, priority, and uncaught-exception handlers), and a RejectedExecutionHandler (decides what happens when a task can't be accepted). Together these control how many threads run, how tasks are buffered, how idle capacity is reclaimed, and how overload is handled. Choosing them well is the difference between a responsive pool and one that silently drops work or exhausts memory.
code
java · 15 linesThreadPoolExecutor pool = new ThreadPoolExecutor(
4, // corePoolSize
16, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime for threads above core
new ArrayBlockingQueue<>(100), // bounded workQueue
new ThreadFactory() { // names threads for diagnostics
private final AtomicInteger n = new AtomicInteger();
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "orders-worker-" + n.incrementAndGet());
t.setDaemon(false);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // backpressure on overload
);go deeper
Can name the parameters and say core = kept-alive threads, max = ceiling, queue = waiting tasks.
Explains keep-alive reclamation, that core threads don't time out by default, and what a ThreadFactory/RejectedExecutionHandler are for.
Connects the parameters to real tuning trade-offs and knows the built-in rejection policies and why naming threads matters in incidents.
Treats these as a system-design contract: ties pool sizing to SLAs, backpressure, observability (named threads, metrics), and graceful degradation under overload.
## What a thread pool is Creating an operating-system thread is expensive (memory for its stack, kernel bookkeeping, scheduling). If a program created a brand-new thread for every small task it would waste resources and could run out of memory. A **thread pool** solves this by keeping a fixed-ish set of reusable **worker threads** alive and feeding them a stream of **tasks** (units of work, expressed as `Runnable` or `Callable`). When a task finishes, its thread stays alive and picks up the next task instead of dying. In Java the standard, fully configurable thread pool is **`java.util.concurrent.ThreadPoolExecutor`**. The convenience factory methods on `Executors` (e.g. `Executors.newFixedThreadPool`) are just thin wrappers that build a `ThreadPoolExecutor` with preset arguments. ## The six constructor parameters The most general constructor is: ``` ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) ``` - **corePoolSize** — the number of worker threads the pool tries to keep alive even when they have nothing to do. New tasks normally cause a new core thread to be created until this many exist (see the submission logic question). By default core threads are *not* timed out; you can opt in with `allowCoreThreadTimeOut(true)`. - **maximumPoolSize** — the absolute ceiling on the number of worker threads. The pool only grows past `corePoolSize` toward this number under specific conditions (when the queue is full). - **keepAliveTime** + **unit** — when the pool has more than `corePoolSize` threads, any thread that sits **idle** (no task) for longer than this duration is terminated, shrinking the pool back toward the core size. `unit` is a `TimeUnit` (SECONDS, MILLISECONDS…). This reclaims capacity after a burst of load subsides. - **workQueue** — a `BlockingQueue<Runnable>` that holds tasks submitted while all core threads are busy. A *blocking* queue is one whose operations can wait (block) until space or an element is available; worker threads block on `take()` waiting for the next task. The queue's *type and capacity* hugely affect behavior (bounded vs unbounded vs synchronous — see the queue question). - **threadFactory** — an object whose job is to create each new worker thread. Supplying your own lets you give threads meaningful **names** (priceless in thread dumps/logs), mark them as **daemon** threads (daemons don't keep the JVM alive at shutdown), set **priority**, set a **context class loader**, or install an **UncaughtExceptionHandler**. If you don't pass one, `Executors.defaultThreadFactory()` is used. - **handler** (`RejectedExecutionHandler`) — the policy invoked when a task cannot be accepted, which happens when the queue is full **and** the pool is already at `maximumPoolSize`, or when the pool has been shut down. Built-in policies: `AbortPolicy` (default — throws `RejectedExecutionException`), `CallerRunsPolicy` (the thread that submitted the task runs it itself, which throttles the submitter), `DiscardPolicy` (silently drops the new task), `DiscardOldestPolicy` (drops the oldest queued task and retries). You can also write a custom handler. ## Why each matters - Get **core/max** wrong and you either under-utilize the machine or spawn thousands of threads. - Get the **queue** wrong (e.g. unbounded) and `maximumPoolSize` becomes meaningless and you can run out of heap. - Skip a **ThreadFactory** and every thread is named `pool-1-thread-7`, making production incidents hard to diagnose. - Skip a deliberate **rejection handler** and overload throws exceptions you may not be ready for — or, with a bad choice, silently loses tasks. Understanding all six, and how they interact during task submission, is the foundation of tuning any Java thread pool.
- What does the ThreadFactory let you do that the default doesn't?Give threads descriptive names, mark them daemon/non-daemon, set priority, set the context class loader, and install an UncaughtExceptionHandler — all of which make production debugging and lifecycle control far easier.
- When are threads above corePoolSize actually terminated?When they sit idle (no task) longer than keepAliveTime, the pool reaps them down toward corePoolSize. Core threads are exempt unless you call allowCoreThreadTimeOut(true).
Think of a call center: core agents are always on the clock (corePoolSize), you can call in extra temps up to a hard cap (maximumPoolSize), temps go home after sitting idle a while (keepAliveTime), callers wait in a hold queue (workQueue), HR hires each agent with a badge and rules (threadFactory), and when the queue is full and all temps are working you need a policy for new callers — busy signal, voicemail, or 'you handle it yourself' (RejectedExecutionHandler).
saying these in an interview costs you the question
- Saying maximumPoolSize is the number of threads that normally run (it's only reached under specific queue-full conditions).
- Believing keepAliveTime kills core threads by default (it doesn't unless allowCoreThreadTimeOut is set).
- Thinking the work queue must be unbounded or that any BlockingQueue behaves the same.
- Ignoring ThreadFactory and RejectedExecutionHandler as 'optional fluff' — they decide diagnosability and overload behavior.