从源码入手,过程中遇到不懂的扩展出去,解决完了再回到源码,直到把核心代码理解完。
/**
* An instance of this class is used to generate a stream of
* pseudorandom numbers. The class uses a 48-bit seed, which is
* modified using a linear congruential formula. (See Donald Knuth,
* <i>The Art of Computer Programming, Volume 2</i>, Section 3.2.1.)
* <p>
* If two instances of {@code Random} are created with the same
* seed, and the same sequence of method calls is made for each, they
* will generate and return identical sequences of numbers. In order to
* guarantee this property, particular algorithms are specified for the
* class {@code Random}. Java implementations must use all the algorithms
* shown here for the class {@code Random}, for the sake of absolute
* portability of Java code. However, subclasses of class {@code Random}
* are permitted to use other algorithms, so long as they adhere to the
* general contracts for all the methods.
* <p>
* The algorithms implemented by class {@code Random} use a
* {@code protected} utility method that on each invocation can supply
* up to 32 pseudorandomly generated bits.
* <p>
* Many applications will find the method {@link Math#random} simpler to use.
*
* <p>Instances of {@code java.util.Random} are threadsafe.
* However, the concurrent use of the same {@code java.util.Random}
* instance across threads may encounter contention and consequent
* poor performance. Consider instead using
* {@link java.util.concurrent.ThreadLocalRandom} in multithreaded
* designs.
*
* <p>Instances of {@code java.util.Random} are not cryptographically
* secure. Consider instead using {@link java.security.SecureRandom} to
* get a cryptographically secure pseudo-random number generator for use
* by security-sensitive applications.
*
* @author Frank Yellin
* @since 1.0
*/
复制代码
大致翻一下
重点:
此实例用于生成伪随机数,使用48位种子,改自线性同余方程。
特点:
java.util.concurrent.ThreadLocalRandom java.security.SecureRandom
伪随机数算法的核心。至于为毛用这个算法,怎么保证随机性等等更深的问题,我想过,但特么太复杂了。。。先搞懂这个算法再说吧。。。那些更深的问题,我八成是放弃了,毕竟我是个 Java 开发。(手动狗头)
a*x≡b(mod m)
这个公式的意思是:a*x 和 b 除以 m后余数相同,读作 a*x 与 b 同余,mod 为 c。
X(n+1) = (a * X(n) + c) % m
其中 X(0) 为种子, X(0) 的值算出 X(1) , X(1) 的值算出 X(2) ...
依次类推,一旦种子( X(0) )确定,随机数队列即确定的,也是特点 1 的原因。
看哈 Random 类怎么实现的 X(0) 的,有两个构造器,一个传种子,一个不传。
// 不传参的构造器
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;
}
}
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);
}
}
复制代码
无参构造器获取种子的方法是,用 current(8682522807148012L) 乘以 181783497276652981L 。
compareAndSet 方法保证线程安全,如果检测到 current 值被其他线程修改了就再乘一次, for (;;) 相当于 while(ture) 。
接着再用结果 next 异或 System.nanoTime() ,最后得到的结果就是 seed 。
System.nanoTime() 有点像 System.currentTimeMillis() ,是一个跟时间有关的随机数,但他只能用来计算时间段,比如方法体前后分别调用 System.nanoTime() 再相减可以计算方法执行时间,但不能用于计算当前日期。
线性同余出现在 next 方法中。这是 Random 类的核心,所有计算随机数的方法都调用次方法。
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));
}
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
复制代码
核心代码 nextseed = (oldseed * multiplier + addend) & mask 跟 X(n+1) = (a * X(n) + c) % m 是等价的。
multiplier、addend、mask 分别对应 a、c、m , % m 变成了 & mask 。
网上这么解释的:
所以 x & [(1L << 48)–1] 与 x(mod 2^48) 等价。
好了好了,求放过,Java位运算符我知其然不知其所以然,还有为毛线性同余方程可以用来做随机数算法我也不知道,不想假装知道假装高深(手动笑哭),希望懂的大佬们带带我。。。(手动狗头),哈哈哈哈。
每一次成长,都想与你分享。