Java Random源码分析

一、Random类的构造函数,官方文档如下:

Random类的构造函数

1.无参的构造函数,源码如下:

    /**
     * Creates a new random number generator. This constructor sets
     * the seed of the random number generator to a value very likely
     * to be distinct from any other invocation of this constructor.
     */
    public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }

其中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;
        }
    }
    /**
     * Returns the current value of the running Java Virtual Machine's
     * high-resolution time source, in nanoseconds.
     *
     * <p>This method can only be used to measure elapsed time and is
     * not related to any other notion of system or wall-clock time.
     * The value returned represents nanoseconds since some fixed but
     * arbitrary <i>origin</i> time (perhaps in the future, so values
     * may be negative).  The same origin is used by all invocations of
     * this method in an instance of a Java virtual machine; other
     * virtual machine instances are likely to use a different origin.
     *
     * <p>This method provides nanosecond precision, but not necessarily
     * nanosecond resolution (that is, how frequently the value changes)
     * - no guarantees are made except that the resolution is at least as
     * good as that of {@link #currentTimeMillis()}.
     *
     * <p>Differences in successive calls that span greater than
     * approximately 292 years (2<sup>63</sup> nanoseconds) will not
     * correctly compute elapsed time due to numerical overflow.
     *
     * <p>The values returned by this method become meaningful only when
     * the difference between two such values, obtained within the same
     * instance of a Java virtual machine, is computed.
     *
     * <p> For example, to measure how long some code takes to execute:
     *  <pre> {@code
     * long startTime = System.nanoTime();
     * // ... the code being measured ...
     * long estimatedTime = System.nanoTime() - startTime;}</pre>
     *
     * <p>To compare two nanoTime values
     *  <pre> {@code
     * long t0 = System.nanoTime();
     * ...
     * long t1 = System.nanoTime();}</pre>
     *
     * one should use {@code t1 - t0 < 0}, not {@code t1 < t0},
     * because of the possibility of numerical overflow.
     *
     * @return the current value of the running Java Virtual Machine's
     *         high-resolution time source, in nanoseconds
     * @since 1.5
     */
    public static native long nanoTime();

注:其中nanoTime()函数,用native关键字修饰,由本地代码实现。

两者异或运算后,初始化类成员变量seed,此变量会成为随机函数的种子,所有的随机算法都由种子数算起,定义如下:

    /**
     * The internal state associated with this pseudorandom number generator.
     * (The specs for the methods in this class describe the ongoing
     * computation of this value.)
     */
    private final AtomicLong seed;

2.有参的构造函数,源码如下:

    /**
     * Creates a new random number generator using a single {@code long} seed.
     * The seed is the initial value of the internal state of the pseudorandom
     * number generator which is maintained by method {@link #next}.
     *
     * <p>The invocation {@code new Random(seed)} is equivalent to:
     *  <pre> {@code
     * Random rnd = new Random();
     * rnd.setSeed(seed);}</pre>
     *
     * @param seed the initial seed
     * @see   #setSeed(long)
     */
    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);
        }
    }

构造函数由传入的种子参数进行初始化,不难理解,值得注意的是,由同一个种子初始化的Random对象,相同次数调用时,产生的随机数相同。举例如下:

public static void main(String[] args) {
    Random random = new Random(47);
    for(int i = 0; i < 3; i++){
        System.out.print(random.nextInt(20) + " ");
    }
    System.out.println();
    Random randomII = new Random(47);
    for(int i = 0; i < 3; i++){
        System.out.print(randomII.nextInt(20)+ " ");
    }
}

输出如下:
18 15 13 
18 15 13 

二、Java随机数原理

大多数语言实现伪随机数,均采用线性同余方程(linear congruential generator),详细内容参照「资料1」.
Java中使用的是48-bit的种子,然后调用线性同余方程,源码如下:

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

经常在随机数中调用的函数nextInt(),源码如下:

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);

    int r = next(31);
    int m = bound - 1;
    if ((bound & m) == 0)  // i.e., bound is a power of 2
        r = (int)((bound * (long)r) >> 31);
    else {
        for (int u = r;
             u - (r = u % bound) + m < 0;
             u = next(31))
            ;
    }
    return r;
}

此处实现应该是最新版,与资料中提供的源码略有不同,函数中几处细节尚未理解,想清楚之后再回来补充。


Reference:

  1. 开源中国:Java中的random函数是如何实现的
  2. System.nanoTime与System.currentTimeMillis的区别
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 方法1 (数据类型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math...
    GB_speak阅读 41,200评论 2 6
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,778评论 18 399
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 4,673评论 1 114
  • 一、给自己设限。“这次数学考砸了,我就是学不好数学,女生就是适合读文科一些”、“我是会计专业毕业的,我怎么能进营销...
    圆舞amour阅读 207评论 1 2
  • 感想(上层饼干): 随着教育的普及,大家愈来愈知道读书的重要性。 可能从小到大,大家都读过很多书,但是很多内容都忘...
    yangxiaoya阅读 247评论 0 0