11 Pseudorandom Number Generators

Random number generators included in Java SE are more accurately called pseudorandom number generators (PRNGs). They create a series of numbers based on a deterministic algorithm.

The most important interfaces and classes are RandomGenerator, which enables you to generate random numbers of various primitive types given a PRNG algorithm, and RandomGeneratorFactory, which enables you to create PRNGs based on characteristics other than the algorithm's name.

See the java.util.random package for more detailed information about the PRNGs implemented in Java SE.

Characteristics of PRNGs

Because PRNGs generate a sequence of values based on an algorithm instead of a “random” physical source, this sequence will eventually restart. The number of values a PRNG generates before it restarts is called a period.

The state cycle of a PRNG consists of the sequence of all possible values a PRNG can generate. The state of a PRNG is the position of the last generated value in its state cycle.

In general, to generate a value, the PRNG bases it on the previously generated value. However, some PRNGs can generate a value many values further down the sequence without calculating any intermediate values. These are called jumpable PRNGs because they could jump far ahead in the sequence of values, usually by a fixed distance, typically 264. A leapable PRNG can jump even further, typically 2128 values. An arbitrarily jumpable PRNG can jump to any value in the generated sequence of values.

The java.util.Random Class Compared to Other PRNGs

The java.util.random.RandomGeneratorFactory class enables you to create various PRNGs, many of which are in the jdk.random package. The most significant difference between the PRNGs in jdk.random and the java.util.Random class is that Random has a very short period: only 248 values.

Generating Pseudorandom Numbers with RandomGenerator Interface

The following example demonstrates the basic way to create a PRNG and use it to generate a random number:

        RandomGenerator random1 = RandomGenerator.of("Random");
        long value1 = random1.nextLong();
        System.out.println(value1);

It uses the method RandomGenerator.of(String). The argument of this method is the algorithm name of the PRNG. Java SE contains many PRNG classes. Unlike Random, however, most of them are in the jdk.random package.

The RandomGenerator interface contains many methods such as nextLong(), nextInt(), nextDouble(), and nextBoolean() to generate a random number of various primitive data types.

The following example demonstrates how to create a PRNG using the RandomGeneratorFactory class:

        RandomGeneratorFactory<RandomGenerator> factory2 =
            RandomGeneratorFactory.of("SecureRandom");
        RandomGenerator random2 = factory2.create();
        long value2 = random2.nextLong();
        System.out.println(value2); 

To obtain a list of PRNGs implemented by Java SE, call the RandomGeneratorFactory.all() method:

        RandomGeneratorFactory.all()
            .map(f -> f.name())
            .sorted()
            .forEach(n -> System.out.println(n));

This method returns a stream of all the available RandomGeneratorFactory instances available.

You can use the RandomGeneratorFactory class to create PRNGs based on characteristics other than an algorithm’s name. The following example finds the PRNG with the longest period, and creates a RandomGeneratorFactory based on this characteristic:

        RandomGeneratorFactory<RandomGenerator> greatest =
            RandomGeneratorFactory
                .all()
                .sorted((f, g) -> g.period().compareTo(f.period()))
                .findFirst()
                .orElse(RandomGeneratorFactory.of("Random"));
        System.out.println(greatest.name());
        System.out.println(greatest.group());
        System.out.println(greatest.create().nextLong());

Generating Pseudorandom Numbers in Multithreaded Applications

If multiple threads in your application are generating sequences of values using PRNGs, then you want to ensure that there’s no chance that these sequences contain values that coincide with each other, especially if they’re using the same PRNG algorithm. (You would want to use the same PRNG algorithm to ensure that all your application’s pseudorandom number sequences have the same statistical properties.) Splittable, jumpable, and leapable PRNGs are ideal for this; they can create a stream of generators that have the same statistical properties and are statistically independent.

There are two techniques you can use to incorporate PRNGs into your applications. You can dynamically create a new generator when an application needs to fork a new thread. Alternatively, you can create a stream of RandomGenerator objects based on an initial RandomGenerator, then map each RandomGenerator object from the stream to its own thread.

Dynamically Creating New Generators

If you’re using a PRNG that implements the RandomGenerator.SplittableGenerator interface, then when a thread running in your application needs to fork a new thread, call the split() method. It creates a new generator with the same properties as the original generator. It does this by partitioning the original generator’s period into two; each partition is for the exclusive use of either the original or new generator.

The following example uses the L128X1024MixRandom PRNG, which implements the RandomGenerator.SplittableGenerator interface. The IntStream processes stream represents tasks intended to be run on different threads.

        int NUM_PROCESSES = 100;
        
        RandomGeneratorFactory<SplittableGenerator> factory =
            RandomGeneratorFactory.of("L128X1024MixRandom");
        SplittableGenerator random = factory.create();
       
        IntStream processes = IntStream.rangeClosed(1, NUM_PROCESSES);
        
        processes.parallel().forEach(p -> {
            RandomGenerator r = random.split();
            System.out.println(p + ": " + r.nextLong());
        });

Splittable PRNGs generally have large periods to ensure that new objects resulting from a split use different state cycles. But even if two instances "accidentally" use the same state cycle, they are highly likely to traverse different regions of that shared state cycle.

Creating Stream of Generators

If the initial generator implements the interface RandomGenerator.StreamableGenerator, then call the method rngs(), jumps() (for jumpable generators), or leaps() (for leapable generators) to create a stream of generators. Call the map() method on the stream to assign each generator to its own thread.

When you call the jumps() method, the generator changes its state by jumping forward a large fixed distance within its state cycle, then creates a new generator based on the generator’s new state. The generator repeatedly jumps and creates generators, creating a stream of generators. The leaps() method is similar; the size of the jump is much larger.

The following example creates a jumpable generator, then creates a stream of generators based on this initial generator by calling the jumps() method. The first several generators in the stream (defined by NUM_TASKS) are wrapped in a Task instance, then each Task is run in its own thread.

       int NUM_TASKS = 10;
       
       RandomGeneratorFactory<JumpableGenerator> factory =
            RandomGeneratorFactory.of("Xoshiro256PlusPlus");
       JumpableGenerator random = factory.create();
       
       class Task implements Runnable {
            private int p;
            private RandomGenerator r;
            public Task(RandomGenerator prng) {
                r = prng;
            }
            public void run() {
                System.out.println(r.nextLong());
            }
        }
        
        List<Thread> taskList = random
            .jumps()
            .limit(NUM_TASKS)
            .map(prng -> new Thread(new Task(prng)))
            .collect(Collectors.toList());
        taskList.stream().forEach(t -> t.start());

Choosing a PRNG Algorithm

For applications (such as physical simulation, machine learning, and games) that don't require a cryptographically secure algorithm, the java.util.random package provides multiple implementations of interface RandomGenerator that focus on one or more PRNG properties, which include speed, space, period, accidental correlation, and equidistribution.

Note:

As PRNG algorithms evolve, Java SE may add new PRNG algorithms and deprecate older ones. It's recommended that you don't use deprecated algorithms; they may be removed from a future Java SE release. Check if an algorithm has been deprecated by calling either the RandomGenerator.isDeprecated() or RandomGeneratorFactory.isDeprecated() method.

Cryptographically Secure

For applications that require a random number generator algorithm that is cryptographically secure, use the SecureRandom class in the java.security package.

See The SecureRandom Class in Java Platform, Standard Edition Security Developer's Guide for more information.

General Purpose

For applications with no special requirements, L64X128MixRandom balances speed, space, and period well. It's suitable for both single-threaded and multithreaded applications when used properly (a separate instance for each thread).

Single-Threaded, High Performance

For single-threaded applications, Xoroshiro128PlusPlus is small, fast, and has a sufficiently long period.

32-Bit Applications

For applications running in a 32-bit environment and using only one or a small number of threads, L32X64StarStarRandom or L32X64MixRandom are good choices.

Multithreaded Applications with Static Threads

For applications that use many threads that are allocated in one batch at the start of computation, consider a jumpable generator such as Xoroshiro128PlusPlus or Xoshiro256PlusPlus or a splittable generator such as L64X128MixRandom or L64X256MixRandom. If your application uses only floating-point values from a uniform distribution where no more than 32 bits of floating-point precision is required and exact equidistribution is not required, then MRG32k3a, a classic and well-studied algorithm, may be appropriate.

Multithreaded Applications with Dynamic Threads

For applications that create many threads dynamically, perhaps through the use of spliterators, a splittable generator such as L64X128MixRandom or L64X256MixRandom is recommended.

If the number of generators created dynamically may be very large (millions or more), then using generators such as L128X128MixRandom or L128X256MixRandom will make it much less likely that two instances use the same state cycle.

Tuples of Consecutively Generated Values

For applications that use tuples of consecutively generated values, consider a generator that is k-equidistributed such that k is at least as large as the length of the tuples being generated. For example, the generator L64X256MixRandom is shown to be 4-equidistributed, which means that you can have a sequence of tuples that contain four values, and these tuples will be uniformly distributed (there’s an equal chance that any 4-tuple will appear in the sequence). It’s also shown that L64X1024MixRandom is 16-equidistributed.

Large Permutations

For applications that generate large permutations, consider a generator whose period is much larger than the total number of possible permutations; otherwise, it will be impossible to generate some of the intended permutations. For example, if the goal is to shuffle a deck of 52 cards, the number of possible permutations is 52! (52 factorial), which is approximately 2225.58, so it may be best to use a generator whose period is roughly 2256 or larger, such as L64X256MixRandom, L64X1024MixRandom, L128X256MixRandom, or L128X1024MixRandom.