Random与ThreadLocalRandom 解析
问题:既然已经有了 Random 为什么还需要 ThreadLocalRandom?
正文
Random 是使用最广泛的随机数生成工具了,即使连 Math.random() 的底层也是用 Random 实现的 Math.random()
源码如下:
可以看出 Math.random()
直接指向了 Random.nextDouble()
方法。
Random 使用
这开始之前,我们先来了解一下 Random 的使用。
Random random = new Random();
for (int i = 0; i < 3; i++) {// 生成 0-9 的随机整数random.nextInt(10);
}
复制代码
以上程序的执行结果为:
1
0
7
Random 源码解析
可以看出 Random 是通过 nextInt()
方法生成随机整数的,那他的底层的是如何实现的呢?我们来看他的实现源码:
/*** 源码版本:JDK 11*/
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 2r = (int)((bound * (long)r) >> 31);else {for (int u = r;u - (r = u % bound) + m < 0;u = next(31));}return r;
}
复制代码
从以上源码我们可以看出,整个源码最核心的部分有两块:
- 根据老种子生成新种子;
- 根据新种子计算出随机数。
根据新种子计算出随机数的代码已经很明确了,我们需要确认一下 next()
方法是如何实现的,继续看源码:
/*** 源码版本:JDK 11*/
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)); // 使用 CAS 更新种子return (int)(nextseed >>> (48 - bits));
}
复制代码
根据以上源码可以看出,在使用老种子去获取新种子的时候,如果是多线程操作,则同一时刻只会有一个线程 CAS (Conmpare And Swap,比较并交换) 成功,其他失败的线程会通过自旋等待获取新种子,因此会有一定的性能消耗。
这也是为什么 JDK 1.7 会引入 ThreadLocalRandom 的答案了,它的出现主要为了提升多线程情况下 Random 的执行效率。那它是如何来提升的?接下来一起来看。
ThreadLocalRandom 使用
我们先来看 ThreadLocalRandom 的类关系图:
可以看出 ThreadLocalRandom 继承于 Random 类,先来看它的使用:
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 3; i++) {// 生成 0-9 的随机数System.out.println(threadLocalRandom.nextInt(10));
}
复制代码
以上程序的执行结果为:
1
7
5
可以看出 ThreadLocalRandom 和 Random 一样,都是通过 nextInt()
方法实现随机整数生成的。
ThreadLocalRandom 源码解析
接下来我们来看 ThreadLocalRandom 的随机数是如何生成的,源码如下:
/*** 源码版本:JDK 11*/
public int nextInt(int bound) {if (bound <= 0)throw new IllegalArgumentException(BAD_BOUND);// 根据老种子生成新种子int r = mix32(nextSeed());int m = bound - 1;// 根据新种子计算算出随机数if ((bound & m) == 0) // power of twor &= m;else { // reject over-represented candidatesfor (int u = r >>> 1;u + m - (r = u % bound) < 0;u = mix32(nextSeed()) >>> 1);}return r;
}
复制代码
从以上源码可以看出 ThreadLocalRandom 的 nextInt()
和 Random 的 nextInt()
在写法和实现思路都很像,他们主要的区别在 nextSeed()
方法上,源码如下:
/*** 源码版本:JDK 11*/
final long nextSeed() {Thread t; long r; // read and update per-thread seed// 把当前线程作为参数生成一个新种子U.putLong(t = Thread.currentThread(), SEED,r = U.getLong(t, SEED) + GAMMA);return r;
}
@HotSpotIntrinsicCandidate
public native void putLong(Object o, long offset, long x);
复制代码
从以上源码可以看出,ThreadLocalRandom 并不是像 Thread 那样使用 CAS 和自旋来获取新种子,而是在每个线程中使用每个线程中保存自己的老种子来生成新种子,因此就可以避免多线程竞争和自旋等待的时间,所以在多线程环境下性能更高。
ThreadLocalRandom 注意事项
在使用 ThreadLocalRandom 时需要注意一下,在多线程不能共享一个 ThreadLocalRandom 对象,否则会造成生成的随机数都相同,如下代码所示:
// 声明多线程
ExecutorService service = Executors.newCachedThreadPool();
// 共享 ThreadLocalRandom
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {// 多线程执行随机数并打印结果service.submit(() -> {System.out.println(Thread.currentThread().getName() + ":" + threadLocalRandom.nextInt(10));;});
}
复制代码
以上程序执行结果如下:
pool-1-thread-2:4
pool-1-thread-1:4
pool-1-thread-3:4
pool-1-thread-10:4
pool-1-thread-6:4
pool-1-thread-7:4
pool-1-thread-4:4
pool-1-thread-9:4
pool-1-thread-8:4
pool-1-thread-5:4
Random VS ThreadLocalRandom
Random 生成获取新种子,如下图所示:
ThreadLocalRandom 生成获取新种子,如下图所示:
性能对比
接下来我们使用 Oracle 官方提供的性能测试工具 JMH (Java Microbenchmark Harness,JAVA 微基准测试套件),来测试一下 Random 和 ThreadLocalRandom 的吞吐量(单位时间内成功执行程序的数量):
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;/*** JDK:11* Windows 10 I5-4460/16G*/
@BenchmarkMode(Mode.Throughput) // 测试类型:吞吐量
//@Threads(16)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class RandomExample {public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(RandomExample.class.getSimpleName()) // 要导入的测试类.warmupIterations(5) // 预热 5 轮.measurementIterations(10) // 度量10轮.forks(1).build();new Runner(opt).run(); // 执行测试}/*** Random 性能测试*/@Benchmarkpublic void randomTest() {Random random = new Random();for (int i = 0; i < 10; i++) {// 生成 0-9 的随机数random.nextInt(10);}}/*** ThreadLocalRandom 性能测试*/@Benchmarkpublic void threadLocalRandomTest() {ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();for (int i = 0; i < 10; i++) {threadLocalRandom.nextInt(10);}}
}
复制代码
测试结果如下:
其中,Cnt 表示运行了多少次,Score 表示执行的成绩,Units 表示每秒的吞吐量。
从 JMH 测试的结果可以看出,ThreadLocalRandom 在并发情况下的吞吐量约是 Random 的 5 倍。
总结
本文讲了 Random 和 ThreadLocalRandom 的使用以及源码分析,Random 是通过 CAS 和自旋的方式生成随机数,在多线程模式下同一时刻只能有一个线程通过 CAS 获取到新种子并生成随机数,其他线程只能自旋等待,所以有一定的性能损耗。而在 JDK 1.7 时新增了 ThreadLocalRandom 它的种子保存在各自的线程中,因此不会有自旋等待的过程,所以高并发情况下性能更优秀。
最后,我们通过官方提供的基准测试工具 JMH 得到的结果,ThreadLocalRandom 的性能大约是 Random 的 5 倍,所以在高并发情况下尽量使用 ThreadLocalRandom。
Random与ThreadLocalRandom 解析相关推荐
- Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom
Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom 文中的 Random即:java.util.Random, ThreadLocalRandom 即 ...
- java中随机数Random和ThreadLocalRandom()用法与区别
package com.test;import java.util.Random; import java.util.concurrent.ThreadLocalRandom;public class ...
- Random和ThreadLocalRandom
在日常项目开发中,随机的场景需求经常发生,如红包.负载均衡等等.在 Java 中的,使用随机,一般使用 Random 或者 Math.random().这篇文章中主要就来介绍下 Random,以及在并 ...
- 【java】java 随机数 Random ThreadLocalRandom SecureRandom
文章目录 1.概述 2.ThreadLocalRandom 2.1 Random 2.2 ThreadLocalRandom 2.2.1 验证会重复 2.2.2 解释 2. Random 和 Thre ...
- 【小家java】Java中Random ThreadLocalRandom 设置随机种子获取随机数精讲
相关阅读 [小家java]java5新特性(简述十大新特性) 重要一跃 [小家java]java6新特性(简述十大新特性) 鸡肋升级 [小家java]java7新特性(简述八大新特性) 不温不火 [小 ...
- 《 面试又翻车了》这次竟然和 Random 有关?
来自:Java中文社群 小强最近面试又翻车了,然而令他郁闷的是,这次竟然是栽到了自己经常在用的 Random 上...... 面试问题 既然已经有了 Random 为什么还需要 ThreadLocal ...
- Spring Cloud Gateway网关实现短网址生成、解析、转发
Spring Cloud Gateway网关实现短网址生成.解析.转发 1.概述 2.基础实现 3.路由处理HandlerFunction 4.配置路由 5.测试 1.概述 在一些生成二维码等场景中, ...
- Java中Random详解
目录 伪随机 什么是伪随机数? Java随机数产生原理: Java中常见生成随机数的几种方式 Math.random() Random Random的两种构造方法: 种子的作用数什么? 小结 Thre ...
- 一声叹息,jdk竟然有4个random
我们从jdk8说起.主要是四个随机数生成器.神马?有四个? 接下来我们简单说下这几个类的使用场景,来了解其中的细微差别,和api设计者的良苦用心. java.util.Randomjava.util. ...
最新文章
- VC++读取txt文件指针的变化
- nginx 配置SSL/HTTPS
- 在颜值上,我 Bootstrap 真的没怕过谁
- Android 数据解析——Gson与json
- *【洛谷 - P1025】数的划分(dfs 或 dp 或 母函数,第二类斯特林数Stirling)
- Matlab高级绘图功能
- easyui 分页实现
- html 语音 懒加载,浏览器HTML自带懒加载技术
- java对谷歌不兼容_谷歌浏览器不兼容的一些Js
- java 人脸识别 性别识别
- ios越狱c语言编译器,IT之家学院:iOS越狱插件利器之Flex — App UI修改篇
- 剪辑视频时PR播放卡顿不连贯|如何修复Premiere软件中播放太卡问题
- 人工智能,机器学习, 深度学习框架图
- 微信开发者工具网页h5本地开发,解决微信公众号绑定域名,本地无法调用微信api问题
- c 与易语言程序间通信,易语言与三菱PLC通信-FX系列
- 【精品教程】Android应用开发详解pdf分享
- EVE-NG模拟器简述
- Spring+SpringMVC+Hibernate整合(封装CRUD操作)
- TarsGo 性能提升之路
- 对Restful的理解