Backend Development 34 min read

Understanding ThreadPoolExecutor in Java: Usage, Implementation Details, and Practical Scenarios

This article provides an in‑depth overview of Java's ThreadPoolExecutor, covering why thread pools are needed, various creation methods via Executors, core parameters, internal workflow, source‑code analysis, monitoring, shutdown procedures, and real‑world applications such as Spring @Async and Dubbo integration.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding ThreadPoolExecutor in Java: Usage, Implementation Details, and Practical Scenarios

In modern Java development, many business scenarios require multithreading, and since Java 5 the JDK provides a built-in thread pool implementation. A thread pool is a container of threads that executes a limited number of tasks concurrently, reducing resource consumption and improving system responsiveness.

Why Use a Thread Pool?

Reduce system resource consumption by reusing existing threads.

Improve response speed; tasks can start immediately without waiting for new thread creation.

Control the number of concurrent threads, preventing resource exhaustion and OOM.

Provide advanced features such as scheduling, periodic execution, and configurable thread counts.

Thread Pool Creation Methods

The java.util.concurrent package offers several ways to create thread pools, either directly via ThreadPoolExecutor or using the factory class Executors .

Executors Factory Methods

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
        60L, TimeUnit.SECONDS,
        new SynchronousQueue
());
}

Example usage:

/**
 * Create an unbounded thread pool
 */
public static void createCachedThreadPool() {
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    final CountDownLatch countDownLatch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
        final int currentIndex = i;
        cachedThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
            countDownLatch.countDown();
        });
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("All threads completed");
}

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue
());
}

Example usage:

/**
 * Create a fixed-size thread pool
 */
public static void createFixedThreadPool() {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    final CountDownLatch countDownLatch = new CountDownLatch(5);
    for (int i = 0; i < 5; i++) {
        final int currentIndex = i;
        fixedThreadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
            countDownLatch.countDown();
        });
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("All threads completed");
}

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

Example usage with delayed and periodic tasks:

public static void createScheduledThreadPool() {
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    final CountDownLatch countDownLatch = new CountDownLatch(5);
    for (int i = 0; i < 5; i++) {
        final int currentIndex = i;
        // Delayed task
        scheduledThreadPool.schedule(() -> {
            System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
            countDownLatch.countDown();
        }, 1, TimeUnit.SECONDS);
        // Periodic task
        scheduledThreadPool.scheduleAtFixedRate(() ->
            System.out.println(Thread.currentThread().getName() + ", every 3s execute done."),
            2, 3, TimeUnit.SECONDS);
    }
}

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue
()));
}

Example usage:

/**
 * Create a single‑threaded pool
 */
public static void createSingleThreadPool() {
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
    singleThreadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
}

ThreadPoolExecutor Direct Creation

public void createThreadPoolExecutor() {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10L,
        TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000), new ThreadPoolExecutor.AbortPolicy());
    final CountDownLatch countDownLatch = new CountDownLatch(8);
    for (int i = 0; i < 8; i++) {
        final int currentIndex = i;
        System.out.println("Submitting " + i + "th thread");
        threadPoolExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
            countDownLatch.countDown();
        });
    }
    System.out.println("All submissions done");
    try {
        System.out.println("Waiting for tasks to finish");
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("All threads executed");
}

Core Parameters of ThreadPoolExecutor

The constructor parameters are:

corePoolSize : Minimum number of threads that stay alive.

maximumPoolSize : Maximum number of threads allowed.

keepAliveTime and unit : Time that excess threads stay idle before termination.

workQueue : Queue that holds pending tasks.

threadFactory : Factory to create new threads with custom properties.

handler : Rejection policy when the pool cannot accept new tasks.

ThreadPoolExecutor Internal Structure

The internal state is managed by a single AtomicInteger ctl that encodes both the run state (high 3 bits) and the worker count (low 29 bits). The run states are RUNNING, SHUTDOWN, STOP, TIDYING, and TERMINATED.

Worker Class

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); }
    // lock/unlock methods omitted for brevity
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try { t.interrupt(); } catch (SecurityException ignore) {}
        }
    }
}

execute() Method

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);
}

addWorker() Method

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;
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final 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;
}

runWorker() Method

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.isInterrupted())
                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);
    }
}

getTask() Method

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;
        }
    }
}

processWorkerExit() Method

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly)
        decrementWorkerCount();
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && !workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

Practical Scenarios

Spring @Async Integration

Enable asynchronous execution in Spring Boot:

@Configuration
@EnableAsync
public class AsyncConfig {
    // bean definitions omitted
}

Use @Async on a method:

@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());
}

For better performance, configure a dedicated ThreadPoolTaskExecutor bean and reference it in @Async("threadPoolTaskExecutor") .

Dubbo Thread Pool Isolation

Define a custom provider with a specific thread pool:

<dubbo:provider id="p1" threadpool="myThreadPool" default="false"/>

Implement the custom thread pool:

public class XxxThreadPool implements ThreadPool {
    public Executor getExecutor() {
        // return a configured ExecutorService
    }
}

Register the implementation via SPI (META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool):

myThreadPool=com.xxx.XxxThreadPool

Apply the provider to a Dubbo service:

@DubboService(version = "1.0.0", timeout = 1000, provider = "p1")
public class DubboApiImpl implements DubboApi {
    @Override
    public String hello() {
        return "hello world";
    }
}

These examples demonstrate how ThreadPoolExecutor underpins both framework‑level asynchronous execution and RPC service isolation.

JavaconcurrencyDubboSpringthread poolExecutorServiceThreadPoolExecutor
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login 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.