Using Fixed Thread Pools with CountDownLatch and Phaser for High‑Volume API Requests in Java
This article explains how to replace slow single‑threaded API calls with a fixed‑size Java thread pool combined with CountDownLatch or Phaser for synchronization, achieving a ten‑to‑twenty‑fold performance boost while controlling request pressure.
In many testing scenarios we need to batch‑request APIs, such as resetting thousands of test accounts or generating test data, and the previous single‑threaded serial execution was too slow for large data volumes.
Two approaches were considered: (1) using Java NIO for asynchronous HTTP requests, and (2) using a thread pool to execute requests concurrently.
The second approach was chosen because the project already uses a synchronous HTTP client (making async client migration costly) and because the first approach makes it difficult to control the request rate, which could cause a sudden spike in service load.
Idea : Use a fixed‑size thread pool and wrap each task as a java.lang.Runnable implementation.
Control pressure by setting the thread pool size.
Synchronize threads using java.util.concurrent.CountDownLatch or java.util.concurrent.Phaser , then shut down the pool.
Implementation : Below are examples of using CountDownLatch and Phaser for synchronization.
java.util.concurrent.CountDownLatch
class MocoReset extends OBase {
public static void main(String[] args) {
def keys = DataUtils.getMocoTokens();
ExecutorService pool = ThreadPoolUtil.createFixedPool(10); // create fixed thread pool
CountDownLatch latch = new CountDownLatch(keys.size()); // create sync object
keys.each {
def base = getBase(it)
def balance = new Balance(base)
pool.execute(() -> {
balance.reset()
latch.countDown()
})
}
latch.await()
pool.shutdown()
}
}java.util.concurrent.Phaser
class MocoReset extends Base {
public static void main(String[] args) {
def keys = DataUtils.getMocoTokens();
ExecutorService pool = ThreadPoolUtil.createFixedPool(10); // create fixed thread pool
def phaser = new Phaser(1)
keys.each {
def base = getBase(it)
def balance = new Balance(base)
pool.execute(() -> {
phaser.register()
balance.reset()
phaser.arriveAndDeregister()
})
}
phaser.arriveAndAwaitAdvance()
pool.shutdown()
}
}Conclusion : Multithreaded programming is very effective in testing; keeping the thread count between 10‑20 is safe, and the theoretical efficiency gain is about 10‑20 times.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.