Random--阅读源码从jdk开始

 

目录

Random实例是线程安全

Random类的构造方法

RandomnextInt方法

Random的其他随机方法

Math.random()ThreadLocalRandom

 

 

Random实例是线程安全

 

Random实例是线程安全的,通过源码可以发现其通过CAS指令完成线程安全。首先我们来看下他的主要成员变量AtomicLong种子:

private final AtomicLong seed;

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

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

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

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

 

其他成员变量都是一些静态常量:

    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;
 
    private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) 理解为16进制表示的1.0X10的-53次方,创建随机double值时使用

 

Random类的构造方法

 

Random类的构造方法有两个:第一个是默认构造方法(无参),第二个是参数为指定种子的构造方法。

1、默认构造方法:

public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }

 

该构造方法,通过seedUniquifier()方法获取一个long型值,再通过System.nanoTime()方法获取当前的毫微秒 也是long型值。先对这两个long进行“异或”操作得到一个long值,再调用Random类的第二个构造方法 指定种子的构造方法。

简单的说就是:默认构造方法先通过一系列的计算,计算出一个种子,再调用第二构造方法为成员变量seed赋值。

在来看下默认构造方法中调用的关键方法seedUniquifier,这个方法可以保证在多线程环境下Random在实例化时候的原子性:

private static long seedUniquifier() {
        for (;;) {
            long current = seedUniquifier.get();//常量Long型的8682522807148012L
            long next = current * 181783497276652981L;//跟另外一个常量相乘
            if (seedUniquifier.compareAndSet(current, next))// cas原子性比较赋值
                return next;
        }
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);

 

 

经典的采用自旋的乐观锁实现方式:在一个无限的for循环中,不停的获取期望current和最终赋值next,采用compareAndSet方法对current和前端值进行比较,如果相对,说明拿到锁,为AtomicLong赋新值next。否则一直循环,直道成功赋值为止。超高并发情况下会比较消耗性能,一般情况下无伤大雅。

 

2、参数为指定种子的构造方法

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);//子类自己实现的初始化
        }
    }

 

该构造方法需要一个long型的参数seed,其实默认构造方法只是自己通过一定的算法得到一个seed,再调用该方法。

该构造方法,逻辑比较简单,就是为成员变量seed赋值。else代码块的逻辑,是为了方便用户自己创建Random的子类,实现自己的初始化逻辑。

 

当然Random不是直接使用的用户传入的参数为成员变量seed赋值,而是采用通过调用initialScramble方法计算的值。

 

private static long initialScramble(long seed) {
        return (seed ^ multiplier) & mask;//先”异或”,再”按位与”, multiplier和mask都是静态的final成员
}

 

 

RandomnextInt方法

 

Random产生随机整数的nextInt方法有两个(重载):无参的nextInt()方法,产生在整型最小值到整型最大值范围内随机的整数;带参数的nextInt(int bound)方法,产生整型0bound范围内的整型值,注意边界[0, bound)

1、无参nextInt()方法

public int nextInt() {
        return next(32);//整型的范围:2的负32次方--2的32次方
}
 
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));
}

 

 

核心是 掉用受保护的next方法,参数bitsbit位数(1个字节8bit),采用所谓的“线性同余算法”,有兴趣的可以研究下。

 

2、带参数的nextInt(int bound)方法

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

 

说实话这个方法看得不是很懂,但是按照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;
    }

 

 

这种方式看起来确实没啥问题,但实际运行确不是想象那样,测试代码:

    public static void main(String[] args) {
        int n=2*(Integer.MAX_VALUE /3);//最大值的 三分之二
        int low = 0;
        for (int i=0;i<1000000;i++){
            if(random(n) < n/2){ //调用自定义的random方法产生100万个随机数,上限为n
                low ++; //统计这100万个随机数落在前半部分的次数
            }
        }
        System.out.println(low);
    }

 

 

按照你想象的,执行main方法,打印结果应该在50万左右。但我运行了3次,结果分别为:666656666864666523。按照书中说法,基本是在666666附近。

 

我们该用random自带的nextInt(int bound)方法重试,代码如下:

public class Test {
 
    private static final Random rnd = new Random();
 
    public static void main(String[] args) {
        int n=2*(Integer.MAX_VALUE /3);//最大值的 三分之二
        int low = 0;
        for (int i=0;i<1000000;i++){
            if(rnd.nextInt(n) < n/2){ //调用自定义的random方法产生100万个随机数,上限为n
                low ++; //统计这100万个随机数落在前半部分的次数
            }
        }
        System.out.println(low);
    }
 
}

 

 

我同样执行了3次,打印结果分别为:499831500015500154。现在正常多了,我又重新执行了一次,结果都是我们想要的,基本都在50万附近。不得不服啊。

 

另外可以思考下,怎么生成一个[m,n)的随机数。

 

一般情况下,如果jdk已经帮我们实现了的类库,我们尽量不要自己去重新实现,在jdk没有明确注释建议不要使用的情况下,直接使用即可(当然大神基本除外)。

 

Random的其他随机方法

 

除了intRandom还提供随机生成其他类型值的方法:

1nextLong() 随机生成一个long

public long nextLong() {
        return ((long)(next(32)) << 32) + next(32);
    }

 

可以看到跟生成int的方法差不多,调用了两次next(32)。毕竟long8个字节,int4个字节。

 

2nextBoolean()随机生成boolean

public boolean nextBoolean() {
        return next(1) != 0; //也是调用的next方法
}

 

3nextBytes(byte[] bytes) 生成内容随机的byte值,并放入bytes数组

public void nextBytes(byte[] bytes) {
        for (int i = 0, len = bytes.length; i < len; ) //for循环数组的长度
            for (int rnd = nextInt(),  //随机的int
                     n = Math.min(len - i, Integer.SIZE/Byte.SIZE);
                 n-- > 0; rnd >>= Byte.SIZE)  //int是4个字节,byte1个字节,需要进行转换
                bytes[i++] = (byte)rnd;
}

 

注意如果传入的bytes数组如果有值,会被随机生成的byte覆盖。

 

4nextDouble() 生成随机的double

public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;//double也是8个字节,需要考虑小数位
}

 

 

5nextFloat()生成随机的浮点数

public float nextFloat() {
        return next(24) / ((float)(1 << 24));// 占4个字节,需要考虑小数
}

 

 

6nextGaussian()生成一个伪高斯(“正常地”)分布的均值为0.0,标准差为1.0从此随机数生成器的序列的double值。

 

简单总结下,Random的各种随机方法,最终都是调用protected int next(int bits) 方法实现了,参数是bit位数。另外对int型,还提供了随机生成0到某个上限的之间的随机数。

 

Math.random()ThreadLocalRandom

 

Math.random()返回一个随机的double值,其本质其实是调用RandomnextDouble方法,源码如下:

public static double random() {
        return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
        static final Random randomNumberGenerator = new Random();
}

 

都是static的,好处也许就是在需要生成一个随机double时,不需要每次都new一个新的Random对象。

 

ThreadLocalRandom:从源码中可以看到Random的实例是线程安全的,在超高并发使用Random实例会影响效率,可以考虑使用ThreadLocalRandom变量代替。

 

最后,Random实现了Serializable接口,是可序列化的。并且自定义writeObjectreadObject方法对象序列化进行了优化,关于自定义序列化,可以参考我之前的一遍总结《java序列化用法以及理论()》。这里不再累述。

猜你喜欢

转载自moon-walker.iteye.com/blog/2377722