java.util.Random 实现原理

Random类 (java.util)

Random类中实现的随机算法是伪随机也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。

相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。这点在生成多个随机数字时需要特别注意。(只要种子一样,获取的随机数的序列就是一致的。是一种为随机数的实现,而不是真正的随机数)

Math类中也有一个random方法,该random方法的工作是生成一个[0,1.0)区间的随机小数。Math类中的random方法就是直接调用Random类中的nextDouble方法实现的。

无参构造器:

public Random() {
    this(seedUniquifier() ^ System.nanoTime());
 }
private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

无参的构造方法,里面帮我们自动产生了一个种子,并通过CAS(compareAndSet)自旋方式保证,每次获取的种子不一样,从而保证每次new Random()获取的随机序列不一致。

安全性问题

  • SecureRandom 来实现随机数的生成

产生高强度的随机数,有两个重要的因素:种子和算法。算法是可以有很多的,通常如何选择种子是非常关键的因素。 如Random,它的种子是System.currentTimeMillis(),所以它的随机数都是可预测的, 是弱伪随机数。
强伪随机数的生成思路:收集计算机的各种信息,键盘输入时间,内存使用状态,硬盘空闲空间,IO延时,进程数量,线程数量等信息,CPU时钟,来得到一个近似随机的种子,主要是达到不可预测性。

Random性能问题

每次获取随机数的时候都是使用CAS的方式进行更新种子的值。这样在高并发的环境中会存在大量的CAS重试,导致性能下降。这时建议大家使用ThreadLocalRandom类来实现随机数的生成。

  • ThreadLocalRandom类来实现随机数的生成。

因为ThreadLocalRandom 中的种子存储在Thread对象中,所以高并发获取Random对象时,不会使用CAS来保证每次获取的值不一致。
每个线程维护一个它自己的种子,每个线程需要获取随机数的时候,从当前的Thread对象中获取当前线程的种子,进行获取随机数,性能大大提高。

信心最重要