Backend Development 7 min read

Dynamic Adjustment of Java ThreadPoolExecutor Core Pool Size Based on Queue Length

The article explains how to extend Java's ThreadPoolExecutor by dynamically increasing or decreasing corePoolSize according to the task queue length, using setCorePoolSize, a daemon monitoring thread, and scheduled tasks to improve asynchronous processing performance in Spring Boot applications.

FunTester
FunTester
FunTester
Dynamic Adjustment of Java ThreadPoolExecutor Core Pool Size Based on Queue Length

When using Java thread pools for various requirements, the benefits are clear, but the built‑in fixed policies often fall short as demand grows, prompting the need for a custom extension based on the existing API.

The execution flow of a ThreadPoolExecutor is illustrated, showing that a new thread is created only when the core pool is not full and the work queue is saturated. In performance tests that batch‑initialize large data sets, a very long waiting queue is required, yet the standard core and maximum pool sizes cannot dynamically adjust the number of active threads based on queue length.

The root cause lies in the corePoolSize parameter: once set, it cannot be automatically changed by the default strategy. Using a cached thread pool also fails to accommodate a large number of pending tasks.

Inspecting the source reveals that java.util.concurrent.ThreadPoolExecutor#setCorePoolSize can modify corePoolSize after the pool has started, enabling active thread count adjustments to meet the described needs.

The author proposes a simple scaling policy: if the waiting queue exceeds 100 tasks, increase corePoolSize by one; if the queue length drops to zero, decrease it by one, while keeping the size within predefined bounds.

To avoid immediate scaling on every task submission (which would quickly hit the maximum pool size), a periodic detection mechanism is introduced. A daemon thread runs every second, and an additional 5‑second scheduled check monitors the queue and adjusts the core size accordingly.

/**
 * Execute daemon thread, ensure thread pool shuts down after main method ends
 * @return
 */
static boolean daemon() {
    def set = DaemonState.getAndSet(true)
    if (set) return
    def thread = new Thread(new Runnable() {
        @Override
        void run() {
            SourceCode.noError {
                while (checkMain()) {
                    SourceCode.sleep(1.0)
                    def pool = getFunPool()
                    if (SourceCode.getMark() - poolMark > 5) {
                        poolMark = SourceCode.getMark()
                        def size = pool.getQueue().size()
                        def corePoolSize = pool.getCorePoolSize()
                        if (size > MAX_ACCEPT_WAIT_TASK && corePoolSize < POOL_MAX) {
                            pool.setCorePoolSize(corePoolSize + 1)
                            log.info("线程池自增" + pool.getCorePoolSize())
                        }
                        if (size == 0 && corePoolSize > POOL_SIZE) {
                            pool.setCorePoolSize(corePoolSize - 1)
                            log.info("线程池自减" + pool.getCorePoolSize())
                        }
                    }
                    ASYNC_QPS.times { executeCacheSync() }
                }
                waitAsyncIdle()
            }
            ThreadPoolUtil.shutPool()
        }
    })
    thread.setDaemon(true)
    thread.setName("Daemon")
    thread.start()
}

In a Spring Boot project, the daemon thread may terminate quickly, so the same logic is placed inside a scheduled task instead.

While writing the article, another API was discovered: java.util.concurrent.ThreadPoolExecutor#addWorker . Its second boolean argument determines whether the core pool size or the maximum pool size bounds the new worker. The source comment explains the three‑step execution process of execute() , including the use of addWorker(command, false) to create a new thread when queuing fails, although the method is private and not directly callable.

FunTester Original Recommendations~ 900 Original Collection 2021 Original Collection 2022 Original Collection Interface Function Test Special Performance Test Special -- By FunTester
JavaConcurrencyThreadPoolSpringBootDynamic ScalingScheduled TaskcorePoolSize
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

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.