`

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
分享到:
评论

相关推荐

    openjdk-18 GA源码(jdk18-jdk-18-ga.tar.gz)

    - **Secure Random Number Generator (RNG)**:随机数生成器的改进,确保生成的随机数在统计学上是不可预测的,对于加密和其他安全应用至关重要。 4. **API扩展与改进**: - **Stream API**:Java的流API持续扩展...

    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的源代码;读源码的过程非常难熬,我在以前也曾读过源码,但都坚持的不久,也...

    jdk1.7源码包含util

    通过阅读和分析JDK 1.7的源码,我们可以深入了解这些类和接口的实现细节,这对于优化代码性能、排查问题以及设计自己的数据结构和算法都有很大帮助。同时,这也是学习Java语言和提升编程能力的重要途径。

    Random-Box:Bukkit 插件随机盒

    开发者可以通过阅读源码了解具体的实现细节,例如: - 查看 `src/main/java` 目录下的源码文件,理解各个类的功能。 - 分析 `pom.xml` 或 `build.gradle` 文件,了解项目的构建配置和依赖。 - 检视 `src/main/...

    dangdang-dubbox:dubbox 源码解析

    《Dubbox源码解析——深度探索Java微服务框架》 Dubbox,作为一款基于Spring的RPC框架,由当当网开源,是Java开发者们在构建分布式系统时常常选用的工具。它不仅集成了Apache的Dubbo,还包含了更多的扩展功能,如...

    JAVA扫雷源码下载

    - **随机生成雷**:使用Java的`Random`类生成随机数来决定雷的位置,确保在游戏开始时正确地分布雷。 - **点击事件处理**:监听用户的鼠标点击事件,根据点击位置执行相应的逻辑,如揭示数字或标记雷。 - **边界...

    JDK1.8、JDK1.7、JDK1.6区别看这里

    本文主要对比了JDK1.8、JDK1.7、JDK1.6中的源码,对比阅读,发现修改问题以及改进点,具有一定的参考价值。下面是对ArrayList类的详细介绍。 一、基本性质 ArrayList是Java集合框架中的一个重要类,它实现了List...

    Java Documentation.zip

    - 结合开源项目源码阅读,结合API文档理解代码实现。 总之,Java Documentation.zip提供的API文档是学习和使用Java不可或缺的参考资料,无论是初学者还是经验丰富的开发者,都可以从中受益匪浅。通过深入研究,...

    微服务生态组件之Spring Cloud LoadBalancer详解和源码分析.doc

    下面我们将从常见使用示例开始,一步步分析源码实现原理,最后还通过一个自定义负载均衡算法加深对源码的理解。 概述 Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器,抽象和实现,用来...

    基于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编程...

    dubbo-master.zip

    《深入剖析Dubbo框架——基于dubbo-master源码解析》 Dubbo,作为阿里巴巴开源的一款高性能、轻量级的服务治理框架,一直以来都是Java开发者们学习和应用的重点。它以其强大的服务治理能力,如服务注册与发现、负载...

    mahout-distribution-0.7-src.zip

    《Apache Mahout 0.7源码解析与应用探索》 Apache Mahout 是一个开源机器学习库,专注于大规模数据集的算法实现。该库由Java编写,并采用Maven作为构建工具,提供了一系列用于构建智能应用的高效算法。本文将深入...

    dubbo2.5.7 源码包

    总结,Dubbo 2.5.7的源码包含了服务治理的核心组件和机制,从服务的暴露与引用,到RPC调用的全生命周期管理,再到服务治理的各个层面,都体现了Dubbo的设计理念与技术实力。通过深入研究这些源码,我们可以更深入地...

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

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

    JSP彩色验证码源码

    在Eclipse中操作JSP彩色验证码源码时,你需要确保已经安装了JDK和Tomcat服务器,并且Eclipse配置了相应的JSP和Servlet支持。将源码导入到Eclipse项目中,运行对应的JSP页面,浏览器会展示生成的彩色验证码。同时,你...

    java写的贪食蛇游戏源码

    【标题】"java写的贪食蛇游戏源码"是一个基于Java编程语言开发的贪食蛇游戏项目。在Java中,贪食蛇游戏的实现主要涉及到了面向对象编程、图形用户界面(GUI)设计以及事件处理等多个核心概念。 贪食蛇游戏的基本...

    JAVA Vector源码解析和示例代码

    【JAVA Vector 源码解析和示例代码】 在Java编程语言中,`Vector`类是集合框架的一部分,早在JDK 1.0版本就已经存在。`Vector`类继承自`AbstractList`并实现了`List`, `RandomAccess`, `Cloneable`等接口,提供了...

Global site tag (gtag.js) - Google Analytics