目录
Random实例是线程安全
Random类的构造方法
Random的nextInt方法
Random的其他随机方法
Math.random()和ThreadLocalRandom
Random实例是线程安全
Random实例是线程安全的,通过源码可以发现其通过CAS指令完成线程安全。首先我们来看下他的主要成员变量AtomicLong种子:
private final AtomicLong seed;
AtomicLong 原子操作的Long型,是final修饰的,结合源码可以看出三点内容:
1、seed是final修饰的,也就是说必须要在random的构造方法中进行初始化。为了保证线程安全以后都不能被修改,每次使用必须复制它的一份拷贝,进行变更操作
2、Random类的线程安全是由于AtomicLong是线程安全的,基于其compareAndSet(cas)方法实现。
3、AtomicLong的最大范围是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成员 }
Random的nextInt方法
Random产生随机整数的nextInt方法有两个(重载):无参的nextInt()方法,产生在整型最小值到整型最大值范围内随机的整数;带参数的nextInt(int bound)方法,产生整型0到bound范围内的整型值,注意边界[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方法,参数bits是bit位数(1个字节8个bit),采用所谓的“线性同余算法”,有兴趣的可以研究下。
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次,结果分别为:666656、666864、666523。按照书中说法,基本是在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次,打印结果分别为:499831、500015、500154。现在正常多了,我又重新执行了一次,结果都是我们想要的,基本都在50万附近。不得不服啊。
另外可以思考下,怎么生成一个[m,n)的随机数。
一般情况下,如果jdk已经帮我们实现了的类库,我们尽量不要自己去重新实现,在jdk没有明确注释建议不要使用的情况下,直接使用即可(当然大神基本除外)。
Random的其他随机方法
除了int,Random还提供随机生成其他类型值的方法:
1、nextLong() 随机生成一个long值
public long nextLong() { return ((long)(next(32)) << 32) + next(32); }
可以看到跟生成int的方法差不多,调用了两次next(32)。毕竟long是8个字节,int是4个字节。
2、nextBoolean()随机生成boolean值
public boolean nextBoolean() { return next(1) != 0; //也是调用的next方法 }
3、nextBytes(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覆盖。
4、nextDouble() 生成随机的double值
public double nextDouble() { return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;//double也是8个字节,需要考虑小数位 }
5、nextFloat()生成随机的浮点数
public float nextFloat() { return next(24) / ((float)(1 << 24));// 占4个字节,需要考虑小数 }
6、nextGaussian()生成一个伪高斯(“正常地”)分布的均值为0.0,标准差为1.0从此随机数生成器的序列的double值。
简单总结下,Random的各种随机方法,最终都是调用protected int next(int bits) 方法实现了,参数是bit位数。另外对int型,还提供了随机生成0到某个上限的之间的随机数。
Math.random()和ThreadLocalRandom
Math.random()返回一个随机的double值,其本质其实是调用Random的nextDouble方法,源码如下:
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接口,是可序列化的。并且自定义writeObject和readObject方法对象序列化进行了优化,关于自定义序列化,可以参考我之前的一遍总结《java序列化用法以及理论(三)》。这里不再累述。
相关推荐
- **Secure Random Number Generator (RNG)**:随机数生成器的改进,确保生成的随机数在统计学上是不可预测的,对于加密和其他安全应用至关重要。 4. **API扩展与改进**: - **Stream API**:Java的流API持续扩展...
jdk8源码的阅读理解 导入idea步骤: 阅读顺序: 大致思路 基本类型的包装类(Character放在最后) String、StringBuffer、StringBuilder、StringJoiner、StringTokenizer(补充正则表达式的知识) CharacterIterator、...
jdk1.8.0_151-源码的中文翻译和一些自己的理解 声明 作者现在大四快要毕业,在实习中,为了在未来成为一名架构师,下定决心开始读Java的源代码;读源码的过程非常难熬,我在以前也曾读过源码,但都坚持的不久,也...
通过阅读和分析JDK 1.7的源码,我们可以深入了解这些类和接口的实现细节,这对于优化代码性能、排查问题以及设计自己的数据结构和算法都有很大帮助。同时,这也是学习Java语言和提升编程能力的重要途径。
开发者可以通过阅读源码了解具体的实现细节,例如: - 查看 `src/main/java` 目录下的源码文件,理解各个类的功能。 - 分析 `pom.xml` 或 `build.gradle` 文件,了解项目的构建配置和依赖。 - 检视 `src/main/...
《Dubbox源码解析——深度探索Java微服务框架》 Dubbox,作为一款基于Spring的RPC框架,由当当网开源,是Java开发者们在构建分布式系统时常常选用的工具。它不仅集成了Apache的Dubbo,还包含了更多的扩展功能,如...
- **随机生成雷**:使用Java的`Random`类生成随机数来决定雷的位置,确保在游戏开始时正确地分布雷。 - **点击事件处理**:监听用户的鼠标点击事件,根据点击位置执行相应的逻辑,如揭示数字或标记雷。 - **边界...
本文主要对比了JDK1.8、JDK1.7、JDK1.6中的源码,对比阅读,发现修改问题以及改进点,具有一定的参考价值。下面是对ArrayList类的详细介绍。 一、基本性质 ArrayList是Java集合框架中的一个重要类,它实现了List...
- 结合开源项目源码阅读,结合API文档理解代码实现。 总之,Java Documentation.zip提供的API文档是学习和使用Java不可或缺的参考资料,无论是初学者还是经验丰富的开发者,都可以从中受益匪浅。通过深入研究,...
下面我们将从常见使用示例开始,一步步分析源码实现原理,最后还通过一个自定义负载均衡算法加深对源码的理解。 概述 Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器,抽象和实现,用来...
基于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 ...
random原始代码客观的 开发遵循Java语言规范和Java语法规则的随机生成的程序。 可以对随机生成的代码进行编译,但是生成的代码是一个没有意义的程序,本身没有逻辑。 该应用程序是使用支持Java 1.8 JDK的Java编程...
《深入剖析Dubbo框架——基于dubbo-master源码解析》 Dubbo,作为阿里巴巴开源的一款高性能、轻量级的服务治理框架,一直以来都是Java开发者们学习和应用的重点。它以其强大的服务治理能力,如服务注册与发现、负载...
《Apache Mahout 0.7源码解析与应用探索》 Apache Mahout 是一个开源机器学习库,专注于大规模数据集的算法实现。该库由Java编写,并采用Maven作为构建工具,提供了一系列用于构建智能应用的高效算法。本文将深入...
总结,Dubbo 2.5.7的源码包含了服务治理的核心组件和机制,从服务的暴露与引用,到RPC调用的全生命周期管理,再到服务治理的各个层面,都体现了Dubbo的设计理念与技术实力。通过深入研究这些源码,我们可以更深入地...
源码 本镜像源自于DockerHub镜像。 版本 当前版本 tomcat 8.0.39, java8 说明 容器启动后会自动创建一个具有所有权限的admin用户,并自动生成随机密码。你可以通过查看容器log获得密码,比如 => Creating and admin ...
在Eclipse中操作JSP彩色验证码源码时,你需要确保已经安装了JDK和Tomcat服务器,并且Eclipse配置了相应的JSP和Servlet支持。将源码导入到Eclipse项目中,运行对应的JSP页面,浏览器会展示生成的彩色验证码。同时,你...
【标题】"java写的贪食蛇游戏源码"是一个基于Java编程语言开发的贪食蛇游戏项目。在Java中,贪食蛇游戏的实现主要涉及到了面向对象编程、图形用户界面(GUI)设计以及事件处理等多个核心概念。 贪食蛇游戏的基本...
【JAVA Vector 源码解析和示例代码】 在Java编程语言中,`Vector`类是集合框架的一部分,早在JDK 1.0版本就已经存在。`Vector`类继承自`AbstractList`并实现了`List`, `RandomAccess`, `Cloneable`等接口,提供了...