问题:既然已经有了 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;
}
复制代码

从以上源码我们可以看出,整个源码最核心的部分有两块:

  1. 根据老种子生成新种子;
  2. 根据新种子计算出随机数。

根据新种子计算出随机数的代码已经很明确了,我们需要确认一下 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 解析相关推荐

  1. Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom

    Java中的随机数生成器:Random,ThreadLocalRandom,SecureRandom 文中的 Random即:java.util.Random, ThreadLocalRandom 即 ...

  2. java中随机数Random和ThreadLocalRandom()用法与区别

    package com.test;import java.util.Random; import java.util.concurrent.ThreadLocalRandom;public class ...

  3. Random和ThreadLocalRandom

    在日常项目开发中,随机的场景需求经常发生,如红包.负载均衡等等.在 Java 中的,使用随机,一般使用 Random 或者 Math.random().这篇文章中主要就来介绍下 Random,以及在并 ...

  4. 【java】java 随机数 Random ThreadLocalRandom SecureRandom

    文章目录 1.概述 2.ThreadLocalRandom 2.1 Random 2.2 ThreadLocalRandom 2.2.1 验证会重复 2.2.2 解释 2. Random 和 Thre ...

  5. 【小家java】Java中Random ThreadLocalRandom 设置随机种子获取随机数精讲

    相关阅读 [小家java]java5新特性(简述十大新特性) 重要一跃 [小家java]java6新特性(简述十大新特性) 鸡肋升级 [小家java]java7新特性(简述八大新特性) 不温不火 [小 ...

  6. 《 面试又翻车了》这次竟然和 Random 有关?

    来自:Java中文社群 小强最近面试又翻车了,然而令他郁闷的是,这次竟然是栽到了自己经常在用的 Random 上...... 面试问题 既然已经有了 Random 为什么还需要 ThreadLocal ...

  7. Spring Cloud Gateway网关实现短网址生成、解析、转发

    Spring Cloud Gateway网关实现短网址生成.解析.转发 1.概述 2.基础实现 3.路由处理HandlerFunction 4.配置路由 5.测试 1.概述 在一些生成二维码等场景中, ...

  8. Java中Random详解

    目录 伪随机 什么是伪随机数? Java随机数产生原理: Java中常见生成随机数的几种方式 Math.random() Random Random的两种构造方法: 种子的作用数什么? 小结 Thre ...

  9. 一声叹息,jdk竟然有4个random

    我们从jdk8说起.主要是四个随机数生成器.神马?有四个? 接下来我们简单说下这几个类的使用场景,来了解其中的细微差别,和api设计者的良苦用心. java.util.Randomjava.util. ...

最新文章

  1. VC++读取txt文件指针的变化
  2. nginx 配置SSL/HTTPS
  3. 在颜值上,我 Bootstrap 真的没怕过谁
  4. Android 数据解析——Gson与json
  5. *【洛谷 - P1025】数的划分(dfs 或 dp 或 母函数,第二类斯特林数Stirling)
  6. Matlab高级绘图功能
  7. easyui 分页实现
  8. html 语音 懒加载,浏览器HTML自带懒加载技术
  9. java对谷歌不兼容_谷歌浏览器不兼容的一些Js
  10. java 人脸识别 性别识别
  11. ios越狱c语言编译器,IT之家学院:iOS越狱插件利器之Flex — App UI修改篇
  12. 剪辑视频时PR播放卡顿不连贯|如何修复Premiere软件中播放太卡问题
  13. 人工智能,机器学习, 深度学习框架图
  14. 微信开发者工具网页h5本地开发,解决微信公众号绑定域名,本地无法调用微信api问题
  15. c 与易语言程序间通信,易语言与三菱PLC通信-FX系列
  16. 【精品教程】Android应用开发详解pdf分享
  17. EVE-NG模拟器简述
  18. Spring+SpringMVC+Hibernate整合(封装CRUD操作)
  19. TarsGo 性能提升之路
  20. 对Restful的理解

热门文章

  1. 论文学习 Feature Generating Networks for Zero-Shot Learning
  2. 【每周研报复现】基于阻力支撑相对强度(RSRS)的市场择时
  3. 空间曲线的切线、主法线、副法线
  4. python如何安装torch_Python安装torch模块报错处理
  5. ts 简单的对象深拷贝
  6. C语言标准,ANSI C,ISO C,GNU C
  7. 用::after写背景
  8. pancakeswap薄饼添加流动性后实现永久锁仓
  9. 【重要】有三AI技术专栏作者邀请,5大权益助力共同成长
  10. PhotoZoom图像缩放方法效果对比