重庆分公司,新征程启航

为企业提供网站建设、域名注册、服务器等服务

java.util.Random的原理是什么

这篇文章给大家介绍java.util.Random的原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

10多年的爱辉网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。全网整合营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整爱辉建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联建站从事“爱辉网站设计”,“爱辉网站推广”以来,每个客户项目都认真落实执行。

java.util.Random可以产生int、long、float、double以及Goussian等类型的随机数。这也是它与java.lang.Math中的方法Random()最大的不同之处,后者只产生double型的随机数。

该类的实例被用于生成伪随机数的流。该类使用一个 48 位的种子,它被一个线性同余公式所修改。如果 Random 的两个实例用同一种子创建,对每个实例完成同方法调用序列它们将生成和返回相同的数序列成同一方法调用序列,它们将生成和返回相同的数序列。

示例

public class RandomTest {
 public static void main(String[] args) {
 testRandom();
 System.out.println("---------------------");
 testRandom();
 System.out.println("---------------------");
 testRandom();
 }
 
 public static void testRandom(){
 Random random = new Random(1);
 for(int i=0; i<5; i++){
  System.out.print(random.nextInt()+"\t");
 }
 System.out.println("");
 }
}

输出结果:

java.util.Random的原理是什么

从结果中发现,只要种子一样,获取的随机数的序列就是一致的。是一种伪随机数的实现,而不是真正的随机数。

Random 源码分析

Random 类结构

class Random implements java.io.Serializable {
 private final AtomicLong seed;

 private static final long multiplier = 0x5DEECE66DL;
 private static final long addend = 0xBL;
 private static final long mask = (1L << 48) - 1;
 private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);

有参构造方法

public Random(long seed) {
 if (getClass() == Random.class)
  this.seed = new AtomicLong(initialScramble(seed));
 else {
  // subclass might have overriden setSeed
  this.seed = new AtomicLong();
  setSeed(seed);
 }
}

private static long initialScramble(long seed) {
 return (seed ^ multiplier) & mask;
}

通过传入一个种子,来生成随机数,通过上面的例子发现,种子一样产生的随机数序列一样,如果每次使用想产生不一样的序列,那就只能每次传入一个不一样的种子。

无参构造方法

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自旋方式保证,每次获取的种子不一样,从而保证每次new Random()获取的随机序列不一致。

nextInt() 方法:获取 int 随机数

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));
 return (int)(nextseed >>> (48 - bits));
}

从代码中我们可以发现,只要种子确定后,每次产生的数,都是采用固定的算法进行产生的,所以只要种子确定后,每次产生的序列就是固定的。

每次更新种子的时候是使用的CAS来更新的,如果高并发的环境下,性能是个问题。

安全性问题

试想下,如果这是一个摇奖平台,只要种子确定后,每次产生的序列都一样。这样就可利用这个漏洞来预测下一次开奖的号码,这样容易被一些人钻空子。

jdk建议大家尽量要使用 SecureRandom 来实现随机数的生成。

SecureRandom

SecureRandom是强随机数生成器,主要应用的场景为:用于安全目的的数据数,例如生成秘钥或者会话标示(session ID),在上文《伪随机数安全性》中,已经给大家揭露了弱随机数生成器的安全问题,而使用SecureRandom这样的强随机数生成器将会极大的降低出问题的风险。

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

说的简单点就是,使用加密算法生成很长的一个随机种子,让你无法猜测出种子,也就无法推导出随机序列数。

Random性能问题

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

ThreadLocalRandom 实现原理

Thread 类

java.util.Random的原理是什么

Thread 类中有一个 threadLocalRandomSeed 属性。

ThreadLocalRandom 结构

java.util.Random的原理是什么

SEED 变量是 threadLocalRandomSeed 在 Thread 对象中的偏移量。

ThreadLocalRandom.nextSeed() 方法

java.util.Random的原理是什么

从这个方法中,我们发现,每个线程的种子值都存储在Thread对象的threadLocalRandomSeed 属性中。

结论

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

关于java.util.Random的原理是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


分享文章:java.util.Random的原理是什么
浏览路径:http://cqcxhl.cn/article/iijcci.html

其他资讯

在线咨询
服务热线
服务热线:028-86922220
TOP