源码分析:java.util.Random

Posted 开源java学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析:java.util.Random相关的知识,希望对你有一定的参考价值。


源码分析:java.util.Random

Java Random类源码分析


计算机如何产生随机数的?java中是如何产生随机数的?完全按照规律执行的代码,如何才能产生随机数呢?其实,这都是不可能的!计算机只能产生的伪随机数。即给出一个种子,根据一些复杂的算法,产生了类似随机数的序列。也就是说算法越精密,则随机数越随机。好在java是开源的,我们跟着jdk的源码,一起学习下java产生随机数的原理。


源码分析:java.util.Random


首先,下面我们来看一下这段代码


Random random = new Random(50);

System.out.println(random.nextInt(100));

System.out.println(random.nextInt(100));

Random random1 = new Random(50);

System.out.println(random1.nextInt(100));

System.out.println(random1.nextInt(100)); 


运行结果为:

  17 

  88 

  17 

  88


.
这真是悲剧啊!说好的随机呢?这段代码完全就像声明一个数组,如果你不信,可以多输出几次。


下面我们来看下源码。分析下到底他是一个什么样子的数组。并且如何才能产生我们需要的“伪随机数”。


首先观察构造函数,其实就时找到一个seed,也许你会看到比较复杂的东西,我们先看比较简单的有参构造函数


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);

 }


无参构造函数比较复杂。我就还是研究下吧,注释很容易明白,一切的一切都是为了继承。其实我们只走if(true)流程。


我们看下initialScramble(seed)方法:


private static final long mask = (1L << 48) - 1;

private static final long multiplier = 0x5DEECE66DL;

private static long initialScramble(long seed) {

    return (seed ^ multiplier) & mask;

}


这里估计大部分人会迷茫,这TM是神马鬼?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型的8682522807148012L  

 long current = seedUniquifier.get();

  //跟另外一个常量相乘 

 long next = current * 181783497276652981L;

  // cas原子性比较赋值  

 if (seedUniquifier.compareAndSet(current, next))

    return next;

 }

seedUniquifier() 这个函数是跟多线程相关的,这个数字的特点是比较大,并且跟当前时间异或后更显得随机。Random实例是线程安全的,通过源码可以发现其通过CAS指令完成线程安全。首先我们来看下他的主要成员变量AtomicLong种子:


private final AtomicLong seed;  


AtomicLong 原子操作的Long型,是final修饰的,结合源码可以看出三点内容:

1、seed是final修饰的,也就是说必须要在random的构造方法中进行初始化。为了保证线程安全以后都不能被修改,每次使用必须复制它的一份拷贝,进行变更操作

2、Random类的线程安全是由于AtomicLong是线程安全的,基于其compareAndSet(cas)方法实现。

3、AtomicLong的最大范围是Long,也就是说可以产生随机的Int和随机的long。


seedUniquifier方法采用自旋的乐观锁实现方式:在一个无限的for循环中,不停的获取期望current和最终赋值next,采用compareAndSet方法对current和前端值进行比较,如果相对,说明拿到锁,为AtomicLong赋新值next。否则一直循环,直道成功赋值为止。


源码分析:java.util.Random
真正的随机数的产生要看一下几个方法:



public boolean nextBoolean() {

  return next(1) != 0;

}


这个最简单。核心是 调用了受保护的next方法,参数bits是bit位数(1个字节8个bit),采用所谓的“线性同余算法”:


protected int next(int bits) {  

    long oldseed, nextseed;  

    AtomicLong seed = this.seed; //保证线程安全,copy一个seed值进行操作  

    do {  

        oldseed = seed.get();  

        nextseed = (oldseed * multiplier + addend) & mask;//跟后面的>>>结合起来,构造所谓的“线性同余算法”  

    } while (!seed.compareAndSet(oldseed, nextseed)); //同样的cas原子型操作  

    return (int)(nextseed >>> (48 - bits));  

}  


这里最关键的是 nextseed = (oldseed

          * multiplier + addend) & mask;

这是一个复杂的算法, 只保留了一位二进制数字。当然只能取两种结果。 


public int nextInt() {  

        return next(32);//整型的范围:2的负32次方--2的32次方  

}  

按照effective java里的说法,该方法是具有算法背景的高级工程师花了大量时间设计、实现的(别看代码很短)。该方法已在成千上万的程序里运行了十数年,从未发现过缺陷。文中还特别建议:如果你希望实现位于0和某个上限之间的随机整数,要使用nextInt(int bound)方法。不要企图编写下列类似的方法:

private static final Random rnd = new Random();  

static int random(int n){  

    //先取正数,再对上限n取余  

    return Math.abs(rnd.nextInt()) % n;  

}  

这样用的方式看起来确实没啥问题,但实际运行确不是想象那样,随机性大打折扣。


第四行r为一个无符号的int型’随机数’。if内部解释很清楚,直接看else。整理后的代码是这样,其实就是一直取余数,最终得带的余数小数边界就好了。 

下面说下如果想产生我们认为的足够随机的随机数。其实很简单。调用无参构造函数就好了。

 

最后,Random实现了Serializable接口,是可序列化的。并且自定义writeObject和readObject方法对象序列化进行了优化。,Random的各种随机方法,最终都是调用protected int next(int bits) 方法实现了,参数是bit位数。另外对int型,还提供了随机生成0到某个上限的之间的随机数,希望对大家有帮助。



源码分析:java.util.Random

放下键盘轻松去旅行

—— Hello,Travel ——



关注更多学习资源!



以上是关于源码分析:java.util.Random的主要内容,如果未能解决你的问题,请参考以下文章

Mesos源码分析

Mybatis源码分析

Spring源码分析专题——目录

ARouter源码分析

Handler源码分析

Eureka源码分析(六) TimedSupervisorTask