Backend Development 8 min read

Choosing and Customizing Thread Pools for Java Performance Testing

This article explains how to select and customize a Java thread pool for performance testing, covering parameter considerations, modifications to the standard fixed thread pool, custom thread factories, work queue choices, and provides complete example code for both fixed and TPS models.

FunTester
FunTester
FunTester
Choosing and Customizing Thread Pools for Java Performance Testing

The thread pool is the core executor of a performance testing engine, making its selection crucial; after reviewing common pool types, a custom thread pool is chosen to better manage threads and tasks.

Ensure sufficient thread resources to execute test cases.

Guarantee rapid execution after task submission.

Promote thread reuse to avoid frequent creation and destruction.

Avoid creating more threads than needed.

These requirements map to thread‑pool parameters, which can be based on the java.util.concurrent.Executors#newFixedThreadPool(int) method:

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

Several adjustments are made:

Set keepAliveTime to 60 seconds to increase thread reuse opportunities.

Add a threadFactory parameter to facilitate logging and troubleshooting.

Limit the workQueue size to 1 or replace the default LinkedBlockingQueue with java.util.concurrent.SynchronousQueue .

Demonstration code for the customized pool:

package org.funtester.performance.books.chapter03.section2;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ThreadFactory demo class
 */
public class ThreadFactoryDemo {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactory() {
            AtomicInteger index = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("线程-" + index.incrementAndGet());
                return thread;
            }
        };
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10), threadFactory);
        for (int i = 0; i < 8; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(System.currentTimeMillis() + "  线程池中的线程名称: " + Thread.currentThread().getName());
                    try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); }
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
}

Sample console output shows distinct thread names and reuse:

1699978852559  线程池中的线程名称: 线程-1
1699978852559  线程池中的线程名称: 线程-2
1699978852559  线程池中的线程名称: 线程-3
1699978852663  线程池中的线程名称: 线程-3
1699978852664  线程池中的线程名称: 线程-2
1699978852664  线程池中的线程名称: 线程-1
1699978852768  线程池中的线程名称: 线程-3
1699978852769  线程池中的线程名称: 线程-2

To ensure unique thread names, the ThreadFactory uses an AtomicInteger for thread‑safe counting. The work queue capacity is set to avoid task rejection, and the output confirms the expected behavior.

If a thread is interrupted during execution, a new thread is created, increasing the factory’s counter beyond the pool’s maximum size.

For a TPS model, a much larger maximum thread count is typical, with a very small work‑queue or a java.util.concurrent.SynchronousQueue . Example code:

package org.funtester.performance.books.chapter03.section2;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class TheadPoolForTpsModel {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactory() {
            AtomicInteger index = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("线程-" + index.incrementAndGet());
                return thread;
            }
        };
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 200, 60L, TimeUnit.SECONDS,
                new SynchronousQueue<>(), threadFactory);
        for (int i = 0; i < 8; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(System.currentTimeMillis() + "  线程池中的线程名称: " + Thread.currentThread().getName());
                    try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); }
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
}

Console output demonstrates eight distinct threads handling the tasks.

1700012277892  线程池中的线程名称: 线程-1
1700012277893  线程池中的线程名称: 线程-4
1700012277892  线程池中的线程名称: 线程-3
1700012277892  线程池中的线程名称: 线程-2
1700012277893  线程池中的线程名称: 线程-6
1700012277893  线程池中的线程名称: 线程-5
1700012277893  线程池中的线程名称: 线程-7
1700012277893  线程池中的线程名称: 线程-8

In practice, the maximum thread count should be set conservatively high; dynamic adjustments via the thread‑pool API are possible but not recommended for beginners due to complexity.

The content is excerpted from the book "From Java to Performance Testing" and includes a request for reader support.

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