`

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序列化用法以及理论()》。这里不再累述。

 

 

 

 

1
1
分享到:
评论

相关推荐

    java8源码-jdk8:jdk8源码阅读理解

    jdk8源码的阅读理解 导入idea步骤: 阅读顺序: 大致思路 基本类型的包装类(Character放在最后) String、StringBuffer、StringBuilder、StringJoiner、StringTokenizer(补充正则表达式的知识) CharacterIterator、...

    java1.8源码-jdk1.8.0_151-:阅读Java源码,版本为jdk1.8.0_151,将会同步翻译源码中的文档注释

    jdk1.8.0_151-源码的中文翻译和一些自己的理解 声明 作者现在大四快要毕业,在实习中,为了在未来成为一名架构师,下定决心开始读Java的源代码;读源码的过程非常难熬,我在以前也曾读过源码,但都坚持的不久,也...

    基于SpringBoot+Quartz的轻量级分布式定时任务调度系统源码+项目说明+sql数据库.zip

    基于SpringBoot+Quartz的轻量级分布式定时任务调度系统源码+项目说明+sql数据库.zip 主要技术选型 1、后端: - SpringBoot 2.6.11 - Quartz 2.3.2 - Mybatis-Plus 3.5.3.2 - Httpclient 4.5.13 2、前端: - Layui ...

    javarandom源码-java_code_generator:生成随机的Java源代码

    random原始代码客观的 开发遵循Java语言规范和Java语法规则的随机生成的程序。 可以对随机生成的代码进行编译,但是生成的代码是一个没有意义的程序,本身没有逻辑。 该应用程序是使用支持Java 1.8 JDK的Java编程...

    java8源码-docker-tomcat8:在jre-8上运行Tomcat服务器8的简单docker镜像

    源码 本镜像源自于DockerHub镜像。 版本 当前版本 tomcat 8.0.39, java8 说明 容器启动后会自动创建一个具有所有权限的admin用户,并自动生成随机密码。你可以通过查看容器log获得密码,比如 =&gt; Creating and admin ...

    java基础案例与开发详解案例源码全

    9.6 Random类和Math类240 9.7 本章习题243 第10章 10.1 异常概述246 10.2 使用try和catch捕获异常..2 50 10.3 使用throw和throws引发异常252 10.4 finally关键字255 10.5 getMessage和printStackTrace方法258 10.6 ...

    java范例开发大全(pdf&源码)

    11.2 Random类的使用 318 实例189 随机数 319 实例190 验证码 322 11.3 Date类和Calendar类 324 实例191 使用Date类获取系统的当前时间 324 实例192 使用DateFormat类获取系统的当前时间 325 实例193 使用...

    jstorm2.2.1执行wordcount

    jdk1.8 jstorm2.2.1 执行步骤: 1. 本地正确安装maven 2. 本地正确安装zookeeper,并启动 3. Idea导入项目源码,以maven形式 4. 执行mvn clean compile 4. 可分别运行random或wordcount下topology下的main类

    疯狂JAVA讲义

    1.8 何时开始使用IDE工具 21 学生提问:老师,我想学习Java编程,到底是学习Eclipse好呢,还是学习JBuilder好呢? 21 1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序...

    Java范例开发大全(全书源程序)

    11.2 Random类的使用 318 实例189 随机数 319 实例190 验证码 322 11.3 Date类和Calendar类 324 实例191 使用Date类获取系统的当前时间 324 实例192 使用DateFormat类获取系统的当前时间 325 实例193 使用...

Global site tag (gtag.js) - Google Analytics