Why AtomicInteger Outperforms Random() in High‑Concurrency Random Number Generation
The article analyzes two common random‑number scenarios in software testing, identifies a CPU‑heavy custom random method, benchmarks ThreadLocalRandom, AtomicInteger, and plain int implementations under multi‑ and single‑thread loads, and proposes a lightweight AtomicInteger‑based selector that consistently delivers the best performance.
Problem
During high‑throughput API testing a custom method
com.funtester.frame.SourceCode#random(java.util.List<F>)became a CPU bottleneck. The test suite could generate >500 k QPS on a single machine, but the repeated calls to the random selector limited overall throughput.
Initial Investigation
Profiling showed that the custom random implementation consumed a disproportionate amount of CPU when invoked twice per request (e.g., selecting a driver and a URL for each simulated user). The goal was to replace it with a faster generator.
Alternative Random Generator
Java’s java.util.concurrent.ThreadLocalRandom is the fastest built‑in source. A small helper was added:
/**
* Get a random integer between 1 and <em>num</em> (inclusive).
* @param num upper bound (inclusive)
* @return random integer
*/
public static int getRandomInt(int num) {
return ThreadLocalRandom.current().nextInt(num) + 1;
}Sequential Access Idea
Instead of picking a random element each time, the collection can be traversed sequentially using a rotating index. The idea was demonstrated with a traffic‑replay scenario that simulates 100 000 users:
def funtest = {
random(drivers).getGetResponse(random(urls))
}
new FunQpsConcurrent(funtest).start()Because the test calls random twice per request, the method’s overhead becomes noticeable when QPS reaches the 100 k range.
Multi‑Threaded Benchmark
Three variants were benchmarked under identical load (10 000 selections per thread, 500 threads, 1 000 iterations):
Original random method.
Rotating index backed by java.util.concurrent.atomic.AtomicInteger.
Rotating index backed by a plain int variable.
package com.funtest.groovytest;
import com.funtester.base.constaint.FixedThread;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.Concurrent;
import java.util.concurrent.atomic.AtomicInteger;
class FunTest extends SourceCode {
static int times = 1000;
static int thread = 500;
static def integers = 0..100 as List;
static def integer = new AtomicInteger();
static def i = 0;
static def size = integers.size();
public static void main(String[] args) {
RUNUP_TIME = 0;
new Concurrent(new FunTester(), thread, "Test random performance").start();
}
private static class FunTester extends FixedThread {
FunTester() { super(null, times, true); }
@Override protected void doing() throws Exception {
10000.times { random(integers) } // original
// 10000.times { integers.get(integer.getAndIncrement() % size) } // AtomicInteger
// 10000.times { integers.get(i++ % size) } // int
}
@Override FunTester clone() { return new FunTester(); }
}
}All variants quickly saturated the CPU, so a single run per variant was recorded:
random: 1151
AtomicInteger: 3152
int: 2273
Surprisingly, the AtomicInteger version achieved the highest throughput, suggesting that the atomic increment plus modulo operation incurs less overhead than the original method’s internal randomness.
Single‑Threaded Benchmark
A comparable test was executed in a single‑threaded context (1 000 000 selections):
package com.funtest.groovytest;
import com.funtester.frame.SourceCode;
import java.util.concurrent.atomic.AtomicInteger;
class FunTestT extends SourceCode {
static int times = 1000000;
static def integers = 0..100 as List;
static def integer = new AtomicInteger();
static def i = 0;
static def size = integers.size();
public static void main(String[] args) {
time {
// times.times { random(integers) }
// times.times { integers.get(integer.getAndIncrement() % size) }
times.times { integers.get(i++ % size) }
}, "Random performance test";
}
}Execution times (milliseconds) were:
random: 763 ms
AtomicInteger: 207 ms
int: 270 ms
The AtomicInteger selector was consistently fastest in both multi‑ and single‑threaded scenarios.
Final Implementation
The preferred approach was encapsulated in a reusable method that validates the list and uses a monotonically increasing AtomicInteger index to select elements:
/**
* Randomly select an object from a list using a rotating AtomicInteger index.
* @param list the source list
* @param index an AtomicInteger that provides a monotonically increasing index
* @return the selected element
*/
public static <F> F random(List<F> list, AtomicInteger index) {
if (list == null || list.isEmpty())
ParamException.fail("Array cannot be empty!");
return list.get(index.getAndIncrement() % list.size());
}Replacing the original random method with this lightweight selector eliminated the CPU bottleneck observed in high‑throughput interface testing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
