java random产生随机数_源码分析:java.util.Random

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

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

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;

}

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。否则一直循环,直道成功赋值为止。

fe0513b18e9cc1a6d4720181a2e5e71c.png

真正的随机数的产生要看一下几个方法:

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到某个上限的之间的随机数,希望对大家有帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值