Designing a Flexible Java ThreadPoolExecutor: From Basics to Advanced Features

This article walks through building a custom thread pool executor in Java, starting with a simple thread creation example, evolving through multiple design iterations, and culminating in a full-featured implementation that supports core and maximum pool sizes, task queues, thread factories, and rejection policies.

macrozheng
macrozheng
macrozheng
Designing a Flexible Java ThreadPoolExecutor: From Basics to Advanced Features

Xiao Yu asks Flash to explain thread pools, feeling confused by the many parameters.

Flash starts with the simplest asynchronous execution: new Thread(r).start(); He then defines a basic Executor interface:

public interface Executor {
    void execute(Runnable r);
}

Flash implements a first version of a tool class:

class FlashExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();
    }
}

Realizing the need to control thread creation, they design a worker thread that pulls tasks from a queue, ensuring only a limited number of threads run:

class FlashExecutor implements Executor {
    // corePoolSize, workQueue, etc.
    public void execute(Runnable r) {
        tasks.offer(r);
    }
    // Worker threads continuously take tasks from the queue
}

Flash highlights three major benefits of this design: controlling thread count, decoupling task submission from execution via a queue, and reducing overhead from repeatedly creating and destroying threads.

Further improvements include:

Creating workers on demand instead of all at once, tracking created workers with workCount.

Adding a RejectedExecutionHandler to let callers decide how to handle rejected tasks.

Introducing a ThreadFactory so new threads are created through a user‑provided factory.

They illustrate the enhanced design with diagrams (images omitted for brevity).

The final version mirrors the real ThreadPoolExecutor constructor:

public FlashExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler) {
    // parameter validation omitted
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

The parameters mean:

corePoolSize : number of core threads that stay alive.

maximumPoolSize : maximum number of threads allowed.

keepAliveTime : idle time before non‑core threads are terminated.

unit : time unit for keepAliveTime.

workQueue : thread‑safe blocking queue holding pending tasks.

threadFactory : factory for creating new threads.

handler : policy for handling rejected tasks.

This comprehensive design ensures the thread pool can handle normal loads with core threads, scale up during spikes with additional threads up to maximumPoolSize, and shrink back by terminating idle non‑core threads, while giving callers control over task rejection and thread creation.

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.

javaconcurrencymultithreadingExecutorThreadPoolExecutor
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.