The Java ThreadLocalRandom class is a newer subclass of the Java Random class that is most suitable for use under the following circumstances:
Conversely:
Despite the above limitations, ThreadLocalRandom does offer an advantage over java.util.Random: in practice (as of Java 8), it uses a higher-quality random number generation algorithm that passes many statistical tests, and has a larger period of 2^{64}. As well as being higher quality, ThreadLocalRandom is generally several times faster than java.lang.Random.
The ThreadLocalRandom class can be used essentially as a drop-in replacement for the standard java.util.Random class, except that you do not explicitly create an instance of the class. Instead, you use the static ThreadLocalRandom.current() method to request a predetermined instance. From then on, you can call the usual Random methods on this instance, such as nextInt(), nextDouble() etc. For example, the following code simulates rolling two dice using ThreadLocalRandom:
Random r = ThreadLocalRandom.current(); int dice1 = r.nextInt(6) + 1; int dice2 = r.nextInt(6) + 1;
Note that this means you cannot specify a seed: in effect, you "get what you're given"!
The ThreadLocalRandom uses an algorithm called SplitMix^{1}. SplitMix works by taking a simple seed sequence and, for each number in the sequence, applies a mixing function (effectively a kind of "hash") to each number. The output from that hash function becomes the next random number. For the time being, we can imagine that the seed sequence could be a simple counting sequence (1, 2, 3...); we will come back to the actual sequence used by ThreadLocalRandom shortly. Most of the "magic" to turn this sequence into a sequence of "random" numbers is therefore in the mixing function.
The mixing function has essentially the same goals as a hash function:
The SplitMix mixing function used by ThreadLocalRandom is actually derived from the mixing function of the 64-bit variant of MurmurHash3. It looks as follows:
private static long mix64(long z) { z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L; return z ^ (z >>> 33); }
The mixing function in essence contains two multiplicative LCGs. An MLCG is essentially a degenerate form of an LCG or linear congruential generator, where we multiply by a "magic" constant that is observed to give good randomness, but do not add any constant, and take the size of the primitive itself (in this case, a Java long = 2^{64}) as the modulus. On its own, a single MLCG makes a poor generator, and as with LCGs in general, the bottom bits exhibit much less randomness than the top bits. But in the mixer function, we use two MCLGs, combined with shifts and XOR operations to mix the higher bits in with the lower bits to "distribute" the randommess across the entire long. (This is a little similar to the secondary hash function you may have seen in the implementation of the Java HashMap class.)
Further XORs, shifts and MLCGs may well improve the quality of the output. But in practice, the MurmurHash3 authors found that two multiplications alone with these constants produced good results. The authors of Java's SplitMix algorithm found that combining this mixing function with a simple underlying sequence (see below) produced a random number generator that passes various standard batteries of statistical tests.
As with many of the constants used in random number generators, the two magic constants 0xff51afd7ed558ccdL and 0xc4ceb9fe1a85ec53L were essentially found empirically.
In principle, we could produce a random number generator from the above mix64() method as follows:
Ensuring that we increment the seed by an odd number each time ensures that we would eventually go through all 2^{64} possible seed values: in other words, the period of the generator will be 2^{64}. (If we incremented by 2 each time, for example, we would skip half of the possible values.) But in practice, we can try to choose an increment that itself helps to provide some extra randomness. Intuitively, we would like to use an (odd-valued) increment that:
The chosen increment is termed the gamma. For ThreadLocalRandom, all threads use the same gamma. The value taken is the so-called golden gamma, 0x9e3779b97f4a7c15L: a 64-bit representation of the golden ratio. In other words, it is an irrational number. (A sequence of nubmers constructed by adding an irrational number in this way is sometimes referred to as a Wehl sequence.)
All threads use the same gamma, yet clearly if we have multiple threads each using ThreadLocalRandom, the whole point is that we want each thread to have its own "unpredictable" sequence of numbers. We achieve this, as you might expect, by giving each thread a different starting seed. The first thread can seed the generator on the current time, as you might expect. (Depending on configuration, the current implementation allows either a combination of System.currentTimeMillis() and System.nanoTime(), or a seed dervied from sources of entropy via SecureRandom.) Subsequent threads then take a starting seed itself derived by applying a mixing function to the previous starting seed, etc, so that we hope that overall, the different threads will have starting seeds that are well distributed over the possible range of seeds. This is generally good enough for common situations when multiple threads are performing independent tasks requiring sequences of random numbers.
But of course, the multiple threads will ulimately be taking their random nubmers from different portions of the same overarching sequence of 2^{64} numbers (just as they would if java.lang.Random or any other standard generator was used). Where a large number of threads or processes will work together on a joint task, the SplittableRandom class may be more appropriate.
Nonetheless, for many applications, ThreadLocalRandom is probably the default generator to use unless you specifically require an alternative for any of the reasons outlined above.
See:
Notes:
1. The first implementation of ThreadLocalRandom in Java 7 used the same LCG algorithm as java.util.Random.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants. Follow @BitterCoffey
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.