源码分析:java.util.Random
Posted 开源java学习
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析:java.util.Random相关的知识,希望对你有一定的参考价值。
Java 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;
}
这里估计大部分人会迷茫,这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。否则一直循环,直道成功赋值为止。
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到某个上限的之间的随机数,希望对大家有帮助。
放下键盘轻松去旅行
—— Hello,Travel ——
关注更多学习资源!
以上是关于源码分析:java.util.Random的主要内容,如果未能解决你的问题,请参考以下文章