Why Use a Thread Pool? Parameters, States, and Core Methods Explained

This article explains why thread pools are essential for Java applications, details the seven key parameters of ThreadPoolExecutor, describes the possible thread‑pool states, and walks through the core methods such as execute, addWorker, runWorker and getTask with code examples.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Why Use a Thread Pool? Parameters, States, and Core Methods Explained

Why Use a Thread Pool

Many developers wonder why they should use a thread pool instead of creating threads manually. Threads are heavyweight objects, and frequent creation and destruction can waste resources, so a pool that reuses threads improves performance and resource utilization.

Beyond reuse, a thread pool lets you control the level of concurrency; too many simultaneous threads can exhaust server resources and cause crashes.

Key Parameters of ThreadPoolExecutor

The constructor of ThreadPoolExecutor has seven important arguments:

public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler)

corePoolSize : Number of core threads that stay alive even when idle (unless allowCoreThreadTimeOut is enabled).

maximumPoolSize : Upper bound of total threads (core + non‑core).

keepAliveTime & unit : Time that non‑core threads may stay idle before being terminated.

workQueue : Queue that holds pending Runnable tasks. Common implementations include LinkedBlockingQueue, ArrayBlockingQueue, SynchronousQueue, and DelayQueue. Using an unbounded queue (e.g., the default from Executors) can cause OOM under heavy load, so a bounded queue is recommended.

threadFactory : Factory that creates new threads; if omitted, a default factory is used.

handler : Rejection policy applied when the queue is bounded and the pool cannot accept more tasks. The four standard policies are:

- AbortPolicy: throws RejectedExecutionException (default).
- CallerRunsPolicy: the submitting thread runs the task.
- DiscardOldestPolicy: discards the oldest queued task and enqueues the new one.
- DiscardPolicy: silently discards the new task.

For critical workloads, defining a custom handler is advisable.

Thread‑Pool States

The pool can be in one of five states, represented by bit‑shifted constants:

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

An AtomicInteger ctl stores both the run state and the worker count.

RUNNING – pool accepts new tasks.

SHUTDOWN – no new tasks, existing queued tasks are processed.

STOP – no new tasks, all queued tasks are discarded, and workers are interrupted.

TIDYING – all tasks finished, preparing to terminate.

TERMINATED – pool has completed termination.

When the pool reaches TERMINATED, it no longer accepts any work.

Task Execution – execute

The core method for submitting work is execute(Runnable command). Its logic can be summarised as:

If command is null, throw NullPointerException.

If the current worker count is less than corePoolSize, try to add a core worker to run the task immediately.

Otherwise, attempt to place the task into workQueue. If queuing succeeds, re‑check the pool state; if the pool is not running, remove the task and apply the rejection policy.

If queuing fails (queue full or pool shut down), try to add a non‑core worker; if that also fails, invoke the rejection handler.

public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    } else if (!addWorker(command, false))
        reject(command);
}

Worker Creation – addWorker

addWorker

creates a Worker object, which wraps a Thread and implements Runnable. It checks pool state, ensures the worker count does not exceed limits, and registers the worker in the pool's HashSet<Worker>.

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
    // create and start the worker thread
    Worker w = null;
    boolean workerStarted = false;
    boolean workerAdded = false;
    try {
        w = new Worker(firstTask);
        Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize) largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker Class

The inner Worker class extends AbstractQueuedSynchronizer and implements Runnable. It holds the actual Thread, the first task, and a counter of completed tasks. It provides lock/unlock methods based on AQS to manage exclusive access.

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    public void run() { runWorker(this); }
    // AQS lock methods omitted for brevity
}

Running a Worker – runWorker

The runWorker method repeatedly fetches tasks and executes them until the pool is stopped or no more work is available.

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if (runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; }
                catch (Error x) { thrown = x; throw x; }
                catch (Throwable x) { thrown = x; throw new Error(x); }
                finally { afterExecute(task, thrown); }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

Fetching Tasks – getTask

getTask

obtains the next Runnable from the queue, respecting shutdown state, worker count, and timeout policies.

private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
            if (r != null) return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

Summary

The article walks through why thread pools are beneficial, explains each constructor parameter of ThreadPoolExecutor, describes the possible pool states, and details the core workflow of task submission and execution, including how workers are created, run, and retrieve tasks from the queue.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

multithreadingthread poolJava concurrencyThreadPoolExecutorExecutor Service
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.