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对象中获取当前线程的种子,进行获取随机数,性能大大提高。
信心最重要