In Java, there are many scenarios for generating random numbers, so in this article, we will take an inventory of the four ways to generate random numbers, the differences between them and the corresponding scenarios of each way.
1.Random
Random class was born in JDK 1.0. The random number it generates is pseudo-random number, that is, regular random number. Random uses linear congruential pseudorandom number generator (LGC) pseudo-random number. In the generation of random numbers, the origin number of random algorithm is called seed number. Based on the seed number, a certain transformation is carried out to generate the required random numbers.
Random objects generate the same random number for the same number of times when the number of seeds is the same. For example, for two random objects with the same seed number, the random number generated for the first time is exactly the same, and the random number generated for the second time is also exactly the same. By default, new Random() uses the current nanosecond time as the number of seeds.
① Basic use
Random is used to generate a random number from 0 to 10 (excluding 10). The implementation code is as follows:
// Generate Random object Random random = new Random(); for (int i = 0; i < 10; i++) { // Generate 0-9 random integers int number = random.nextInt(10); System.out.println("Generate random number:" + number); } Copy code
The results of the above procedures are:
② Analysis of advantages and disadvantages
Random uses LGC algorithm to generate pseudo-random numbers, which has the advantages of high execution efficiency and fast generation speed.
Its disadvantage is that if Random seeds are the same, the Random numbers generated each time are predictable (all the same). As shown in the following code, when we set the same seed number for two threads, we will find that the Random number generated each time is also the same:
// Create two threads for (int i = 0; i < 2; i++) { new Thread(() -> { // Create a Random object and set the same seed Random random = new Random(1024); // Generate random number 3 times for (int j = 0; j < 3; j++) { // Generate random number int number = random.nextInt(); // Print generated random number System.out.println(Thread.currentThread().getName() + ":" + number); // Sleep for 200 ms try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------------------"); } }).start(); } Copy code
The results of the above procedures are:
③ Thread safety issues
When we want to use a class, our first concern is: is it thread safe? For Random, Random is thread safe.
PS: thread safety refers to that in a multithreaded scenario, if the execution result of the program is consistent with the expected result, it is called thread safe, otherwise it is non thread safe (also known as thread safety problem). For example, there are two threads. The first thread performs 100000 + + operations, and the second thread performs 100000 operations. The final result should be no increase or decrease. If the final result of the program is inconsistent with expectations, it is non thread safe.
Let's look at Random's implementation source 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)); // CAS (Compare and Swap) generates random numbers return (int)(nextseed >>> (48 - bits)); } Copy code
PS: all the source code of this article comes from JDK 1.8.0_211.
From the above source code, we can see that the bottom layer of Random uses CAS (Compare and Swap) to solve the thread safety problem. Therefore, for the scenario of generating a large number of Random numbers, Random is a good choice.
PS: there are two kinds of atomic operations implemented by Java concurrency mechanism: one is lock and the other is CAS.
CAS is the abbreviation of Compare And Swap, Java util. concurrent. Many classes in atomic, such as atomicinteger, atomicboolean, atomiclong, etc., are implemented using CAS mechanism.
2.ThreadLocalRandom
ThreadLocalRandom is a new class provided by JDK 1.7. It belongs to JUC (java.util.concurrent). Why do you create another ThreadLocalRandom after having Random?
The reason is very simple. From Random's source code above, we can see that Random uses CAS to solve the thread safety problem when generating Random numbers. However * * CAS is very inefficient in the scenario of fierce thread competition * *. The reason is that other lines are always modifying the original value during CAS comparison, which leads to the failure of CAS comparison, So it has to loop all the time to try CAS operation. Therefore, in the scenario of fierce multi-threaded competition, ThreadLocalRandom can be used to solve the problem of low execution efficiency of Random.
When we first see ThreadLocalRandom, we will think of the ThreadLocal class, which is true. The implementation principle of ThreadLocalRandom is similar to ThreadLocal. It is equivalent to giving each thread its own local seed, so as to avoid the additional performance overhead caused by multiple threads competing for one seed.
① Basic use
Next, we use ThreadLocalRandom to generate a random number from 0 to 10 (excluding 10). The implementation code is as follows:
// Get ThreadLocalRandom object ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < 10; i++) { // Generate 0-9 random integers int number = random.nextInt(10); // Print results System.out.println("Generate random number:" + number); } Copy code
The results of the above procedures are:
② Implementation principle
The implementation principle of ThreadLocalRandom is similar to ThreadLocal. It allows each thread to hold its own local seed, which will be initialized when generating random numbers. The implementation source code is as follows:
public int nextInt(int bound) { // Parameter validation if (bound <= 0) throw new IllegalArgumentException(BadBound); // Calculates a new seed based on the seed in the current thread int r = mix32(nextSeed()); int m = bound - 1; // Calculate the random number according to the new seed and bound if ((bound & m) == 0) // power of two r &= m; else { // reject over-represented candidates for (int u = r >>> 1; u + m - (r = u % bound) < 0; u = mix32(nextSeed()) >>> 1) ; } return r; } final long nextSeed() { Thread t; long r; // read and update per-thread seed // Get the threadLocalRandomSeed variable in the current thread, and then accumulate the GAMMA value as a new seed based on the seed // Reuse unsafe Putlong stores the new seed in the threadLocalRandomSeed variable of the current thread UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r; } Copy code
③ Analysis of advantages and disadvantages
ThreadLocalRandom combines Random and ThreadLocal classes and is isolated in the current thread. Therefore, by avoiding competing for the number of operation seeds, it not only achieves better performance in the multi-threaded environment, but also ensures its thread safety.
In addition, unlike Random, ThreadLocalRandom explicitly does not support setting Random seeds. It rewrites Random's setSeed(long seed) method and directly throws an unsupported operationexception exception, thus reducing the possibility of Random number duplication in multiple threads.
The source code is as follows:
public void setSeed(long seed) { // only allow call from super() constructor if (initialized) throw new UnsupportedOperationException(); } Copy code
As long as the setSeed() method is invoked in the program, the UnsupportedOperationException exception is thrown, as shown in the following figure:
Analysis on the shortcomings of ThreadLocalRandom
Although ThreadLocalRandom does not support the method of manually setting random seeds, it does not mean that ThreadLocalRandom is perfect. When we check the source code of ThreadLocalRandom's method of initializing random seeds, we find that its random seeds are also related to the current time by default. The source code is as follows:
private static long initialSeed() { // Try to get the startup parameters of the JVM String sec = VM.getSavedProperty("java.util.secureRandomSeed"); // If the value set by the startup parameter is true, the parameter is a random 8-bit seed 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; } // If the startup parameters are not set, the random seed algorithm related to the current time is used return (mix64(System.currentTimeMillis()) ^ mix64(System.nanoTime())); } Copy code
It can be seen from the above source code that when we set the startup parameter "- Djava.util.secureRandomSeed=true", ThreadLocalRandom will generate a random seed, which can alleviate the problem of predictable random number caused by the same random seed to a certain extent. However, by default, if this parameter is not set, it can be used in multiple threads because the startup time is the same, As a result, multiple threads will generate the same random number in each operation.
3.SecureRandom
SecureRandom inherits from Random, which provides a strong Random number generator for encryption. Unlike Random, SecureRandom collects Random events, such as mouse clicks and keyboard clicks. SecureRandom uses these Random events as seeds. This means that the seed is unpredictable, unlike Random, which uses the number of milliseconds of the current system time as the seed by default, thus avoiding the possibility of generating the same Random number.
Basic use
// Create a SecureRandom object and set the encryption algorithm SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); for (int i = 0; i < 10; i++) { // Generate 0-9 random integers int number = random.nextInt(10); // Print results System.out.println("Generate random number:" + number); } Copy code
The results of the above procedures are: SecureRandom supports two encryption algorithms by default:
- SHA1PRNG algorithm, provider sun security. provider. SecureRandom;
- Nativeprng algorithm, provider sun security. provider. NativePRNG.
Of course, in addition to the above operation methods, you can also choose to use new SecureRandom() to create SecureRandom objects. The implementation code is as follows:
SecureRandom secureRandom = new SecureRandom(); Copy code
Initialize SecureRandom through new. By default, the NativePRNG algorithm will be used to generate random numbers. However, you can also configure the JVM startup parameter "- Djava.security" parameter to modify the algorithm for generating random numbers, or choose to use getInstance("algorithm name") to specify the algorithm for generating random numbers.
4.Math
Math class was born in JDK 1.0. It contains attributes and methods for performing basic mathematical operations, such as elementary exponent, logarithm, square root and trigonometric function. Of course, it also contains the static method Math for generating random numbers Random(), this method will generate a double value from 0 to 1, as shown in the following code.
① Basic use
for (int i = 0; i < 10; i++) { // Generate random number double number = Math.random(); System.out.println("Generate random number:" + number); } Copy code
The results of the above procedures are:
② Expand
Of course, if you want to use it to generate a certain range of int values, you can write as follows:
for (int i = 0; i < 10; i++) { // Generate an integer from 0 to 99 int number = (int) (Math.random() * 100); System.out.println("Generate random number:" + number); } Copy code
The results of the above procedures are:
③ Implementation principle
By analyzing the source code of Math, we can know that when Math is called for the first time When using the random () method, a pseudo-random number generator is automatically created, * * actually * * new Java util. Random(), continue to call Math next time This new pseudo-random number generator is used when using the random () method.
The source code is as follows:
public static double random() { return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble(); } private static final class RandomNumberGeneratorHolder { static final Random randomNumberGenerator = new Random(); } Copy code
summary
In this paper, we introduce four methods of generating random numbers. Among them, Math encapsulates random, so they are similar. Random generates pseudo-random numbers and takes the current nanosecond time as the seed number. In the case of fierce multi-threaded competition, there are some performance problems due to CAS operation, but for most application scenarios, random is enough. When the competition is fierce, ThreadLocalRandom can be used to replace random, but if the security requirements are high, SecureRandom can be used to generate random numbers. Because SecureRandom will collect some random events as random seeds, SecureRandom can be regarded as a tool class to generate real random numbers.
Author: Java Chinese community
Link: https://juejin.cn/post/6973816615209009159
Source: Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.