/** * 使用无锁算法产生唯一的种子 * */ private static long seedUniquifier() { // L'Ecuyer, "Tables of Linear Congruential Generators of // Different Sizes and Good Lattice Structure", 1999 for (;;) { // 获取 seedUniquifier 的当前值 current long current = seedUniquifier.get(); // 当前值和 181783497276652981L 进行乘法运算,并得到结果 next long next = current * 181783497276652981L; // 如果当前值 current 和运算结果 next 不等则修改 seedUniquifier 值为运算结果 next if (seedUniquifier.compareAndSet(current, next)) // 返回种子 next return next; } }
/** 初始值为 8682522807148012L 的 Long 原子类 */ private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
public int nextInt(int bound) { // 如果边界值小于等于 0 if (bound <= 0) // 异常 throw new IllegalArgumentException(BadBound); // 调用 next 方法生成随机数 int r = next(31); // 边界值减去 1 int m = bound - 1; // 如果相与的结果为 0,说明边界值为 2 的幂次方 if ((bound & m) == 0) // i.e., bound is a power of 2 // 重新生成随机数 r = (int)((bound * (long)r) >> 31); else { for (int u = r; u - (r = u % bound) + m < 0; u = next(31)) ; } return r; }
通过以上代码的简单分析,我们可以知道随机数的生成是通过一定的算法算出来的,而且和 seed 有非常紧密的关系:算法是固定的,如果 seed 也固定了,就会造成上述测试结果中那样,生成的随机数完全一致的情况。如果能够“预测”seed 的值(默认的 seed 也是通过算法得到的),就有可能生成一样的随机数,说明 Random 类的安全性还有待提升。
接下来,我们再看看几段 JDK 1.8 官方文档的说明:
An instance of this class is used to generate a stream of pseudorandom numbers. The class uses a 48-bit seed, which is modified using a linear congruential formula. (See Donald Knuth, The Art of Computer Programming, Volume 2, Section 3.2.1.)
该类的实例用于生成伪随机数流。该类使用 48 位种子,使用线性同余公式进行修改。 (参见 Donald Knuth,计算机程序设计的艺术,第 2 卷,第 3.2.1 节。)
If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return identical sequences of numbers.
如果使用相同的种子创建两个 Random 实例,并且为每个实例创建相同的方法调用序列,则它们将生成并返回相同的数字序列。
Instances of java.util.Random are threadsafe. However, the concurrent use of the same java.util.Random instance across threads may encounter contention and consequent poor performance. Consider instead using java.util.concurrent.ThreadLocalRandom in multithreaded designs.
Instances of java.util.Random are not cryptographically secure. Consider instead using java.security.SecureRandom to get a cryptographically secure pseudo-random number generator for use by security-sensitive applications.
我们在上面了解了 Random 类,再来看 Math 工具类就比较简单了。在 Math 类里面有一个如下的私有的静态内部类:
1 2 3 4 5 6
public static double random() { return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble(); } private static final class RandomNumberGeneratorHolder { static final Random randomNumberGenerator = new Random(); }
对!里面就是一个静态的类成员变量 Random。生成随机数就是调用 Random 类的 nextDouble 方法。
ThreadLocalRandom
ThreadLocalRandom 作为 Random 的子类,重写了 Random 里面的方法,并对并发做了管理和维护,所以在多线程场景下表现比 Random 更好。
SecureRandom
SecureRandom 作为 Random 的子类,该类提供加密强随机数生成器,所以在性能上比 Random 就略逊一筹。
小结
如果没有多线程的场景,那就用 Math 来生成随机数吧,毕竟提供现成的工具类,不用自己维护实例。多线程场景,还是使用 ThreadLocalRandom。如果对安全性有一定要求,可以使用 SecureRandom,那么在性能上有一点点的影响。 通过对 Random 源码的部分解析,其中有 3 个彩蛋可以重点记一下: