Mastering Java Random Number Generation: Random, ThreadLocalRandom, SecureRandom & Math
This article reviews four Java random number generation techniques—Random, ThreadLocalRandom, SecureRandom, and Math.random()—detailing their usage, underlying algorithms, thread‑safety, performance trade‑offs, and appropriate scenarios, helping developers choose the right tool for their applications.
1. Random
Random class was introduced in JDK 1.0. It generates pseudo‑random numbers using a linear congruential generator (LGC). The algorithm starts from a seed value; the same seed produces the same sequence. By default, new Random() uses the current nanosecond time as the seed.
① Basic usage
<code>// Create Random object
Random random = new Random();
for (int i = 0; i < 10; i++) {
// Generate 0‑9 random integer
int number = random.nextInt(10);
System.out.println("Generated random number: " + number);
}
</code>Sample output:
② Advantages and disadvantages
Random’s LGC algorithm offers high execution efficiency and fast generation speed.
However, if two Random instances share the same seed, they produce identical, predictable sequences, as shown in the following multithreaded example:
<code>// Create two threads
for (int i = 0; i < 2; i++) {
new Thread(() -> {
// Create Random with same seed
Random random = new Random(1024);
// Generate 3 random numbers
for (int j = 0; j < 3; j++) {
int number = random.nextInt();
System.out.println(Thread.currentThread().getName() + ":" + number);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---------------------");
}
}).start();
}
</code>Result:
③ Thread‑safety
Random is thread‑safe; its implementation uses CAS (Compare‑And‑Swap) to update the seed atomically.
<code>public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
public int nextInt() {
return next(32);
}
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
</code>Because Random relies on CAS, it performs well for most use cases, but heavy contention can degrade performance.
2. ThreadLocalRandom
ThreadLocalRandom was added in JDK 1.7 as part of java.util.concurrent. It gives each thread its own seed, avoiding contention on a shared seed and improving performance in highly concurrent scenarios.
① Basic usage
<code>// Obtain ThreadLocalRandom instance
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
// Generate 0‑9 random integer
int number = random.nextInt(10);
System.out.println("Generated random number: " + number);
}
</code>Sample output:
② Implementation principle
ThreadLocalRandom stores a per‑thread seed that is updated by adding a constant (GAMMA) and writing back with Unsafe. The seed is mixed to produce the random value.
<code>public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
int r = mix32(nextSeed());
int m = bound - 1;
if ((bound & m) == 0) // power of two
r &= m;
else {
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
return r;
}
final long nextSeed() {
Thread t; long r;
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
</code>③ Advantages and disadvantages
ThreadLocalRandom combines the speed of Random with per‑thread isolation, delivering better performance under contention while remaining thread‑safe.
Unlike Random, it does not support setting a seed; calling setSeed throws UnsupportedOperationException, reducing the chance of duplicate sequences across threads.
<code>public void setSeed(long seed) {
// only allow call from super() constructor
if (initialized)
throw new UnsupportedOperationException();
}
</code>Calling setSeed results in an exception, as shown below:
ThreadLocalRandom drawbacks
Although it does not allow manual seed setting, its default seed is derived from the current time. If the JVM is started with -Djava.util.secureRandomSeed=true, a more unpredictable seed is used; otherwise, threads started at the same time may generate identical sequences.
<code>private static long initialSeed() {
// Try to get JVM start parameter
String sec = VM.getSavedProperty("java.util.secureRandomSeed");
if (Boolean.parseBoolean(sec)) {
byte[] seedBytes = java.security.SecureRandom.getSeed(8);
long s = (long)(seedBytes[0]) & 0xffL;
for (int i = 1; i < 8; ++i)
s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
return s;
}
// Fallback to time‑based seed
return (mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));
}
</code>3. SecureRandom
SecureRandom extends Random and provides a cryptographically strong random number generator. It gathers entropy from sources such as mouse movements and keyboard events, making its seed unpredictable.
Basic usage
<code>// Create SecureRandom with specific algorithm
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
for (int i = 0; i < 10; i++) {
int number = random.nextInt(10);
System.out.println("Generated random number: " + number);
}
</code>Sample output:
SecureRandom supports two default algorithms:
SHA1PRNG provided by sun.security.provider.SecureRandom
NativePRNG provided by sun.security.provider.NativePRNG
You can also instantiate it with
new SecureRandom(), which uses NativePRNG by default, or specify an algorithm via
SecureRandom.getInstance("algorithmName"). JVM launch parameters can change the algorithm.
4. Math
The Math class, also introduced in JDK 1.0, offers the static method
Math.random(), which returns a double between 0 (inclusive) and 1 (exclusive). Internally it creates a singleton Random instance.
① Basic usage
<code>for (int i = 0; i < 10; i++) {
double number = Math.random();
System.out.println("Generated random number: " + number);
}
</code>Sample output:
② Extended usage
To generate an int within a specific range, multiply the double by the range size and cast to int:
<code>for (int i = 0; i < 10; i++) {
// Generate an integer from 0‑99
int number = (int) (Math.random() * 100);
System.out.println("Generated random number: " + number);
}
</code>Sample output:
③ Implementation principle
When
Math.random()is first called, it lazily creates a new
java.util.Randominstance and reuses it for subsequent calls.
<code>public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
</code>Summary
This article covered four ways to generate random numbers in Java. Random provides fast pseudo‑random numbers but can suffer from contention under heavy multithreading. ThreadLocalRandom eliminates that contention and is suitable for highly concurrent scenarios. SecureRandom offers cryptographically strong randomness for security‑critical use cases. Math.random() is a thin wrapper around Random and shares its characteristics.
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.
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.