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

小强最近面试又翻车了,然而令他郁闷的是,这次竟然是栽到了自己经常在用的 Random 上......

面试问题

既然已经有了 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,比较并交换) 成功,其他失败的线程会通过自旋等待获取新种子,因此会有一定的性能消耗

当多线程使用同一个老种子来 CAS 的时候,只能有一个线程能够成功,而其他失败的线程只能通过自旋等待,这也是为什么 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 倍

完整基准测试代码下载:https://github.com/vipstone/blog-example/blob/master/blog-example/src/main/java/com/example/RandomExample.java

总结

本文讲了 Random 和 ThreadLocalRandom 的使用以及源码分析,Random 是通过 CAS 和自旋的方式生成随机数,在多线程模式下同一时刻只能有一个线程通过 CAS 获取到新种子并生成随机数,其他线程只能自旋等待,所以有一定的性能损耗。而在 JDK 1.7 时新增了 ThreadLocalRandom 它的种子保存在各自的线程中,因此不会有自旋等待的过程,所以高并发情况下性能更优秀。

最后,我们通过官方提供的基准测试工具 JMH 得到的结果,ThreadLocalRandom 的性能大约是 Random 的 5 倍,所以在高并发情况下尽量使用 ThreadLocalRandom。

参考 & 鸣谢 《Java 并发编程之美》翟陆续

原创不易,期待你的素质三连,ღ( ´・ᴗ・` )比心~

【END】

近期热文

 
  • 因为我说:volatile 是轻量级的 synchronized,面试官让我回去等通知!

  • 有人说:轻量级锁一定比重量级锁快!我忍不住笑了

  • 如何模拟线程池溢出?线程池溢出后会怎样?「视频版」

关注下方二维码,订阅更多精彩内容

朕已阅 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux Debian11使用国内源安装Podman环境

一、Podman简介 Podman 是一个开源的容器运行时项目&#xff0c;可在大多数 Linux 平台上使用。Podman 提供与 Docker 非常相似的功能。正如前面提到的那样&#xff0c;它不需要在你的系统上运行任何守护进程&#xff0c;并且它也可以在没有 root 权限的情况下运行。 Podman 可…

二叉搜索树中第k大元素_二叉搜索树中第K个最小元素

二叉搜索树中第k大元素Problem statement: 问题陈述&#xff1a; Find the k-th smallest element in a given binary search tree (BST). 在给定的二进制搜索树(BST)中找到第k个最小的元素。 Example: 例&#xff1a; K4Kth smallest element in the above binary tree is:…

阿里巴巴Java开发手册建议设置HashMap的初始容量,但设置多少合适呢?

作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;集合是Java开发日常开发中经常会使用到的&#xff0c;而作为一种典型的K-V结构的数据结构&#xff0c;HashMap对于Java开发者一定不陌生。关于HashMap&#xff0c;很多人都对他有一些基本的了解&…

面向.Net程序员的dump分析

背景 Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。在 Windows 系统上&#xff0c; dump 文件分为内核 dump 和用户态 dump 两种。前者一般用来分析内核相关的问题&#xff0c;比如驱动程序&#xff1b;后者一般用来分析用户态程序的问题。 一般…

Linux Debian利用Dockefile将Python的py文件项目代码打包为Docker Podman镜像

1.创建PyCharm工程 使用PyCharm创建testHelloWorld工程&#xff0c;如下图所示&#xff1a; 2.选择本项目下的Python解释器 通过File -> Setting…选择解释器为本工程下的Python解释器。 【备注&#xff1a;一定要将项目python环境依赖存至本项目下&#xff0c;默认依赖本…

Java14发布!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!​

Java 14 在 2020.3.17 日发布正式版了&#xff0c;但现在很多公司还在使用 Java 7 或 Java 8&#xff0c;每当看到 Java 又发布新版本心里就慌得一匹。不过此版本并不是 LTS (长期支持版) 版本&#xff0c;所以不要慌&#xff0c;我们先来了解一下好了&#xff0c;等 LTS 版本发…

PyCharm更换pip源为国内源、模块安装、PyCharm依赖包导入导出教程

一、更换pip为国内源 1.使用PyCharm创建一个工程 2.通过File -> Setting…选择解释器为本工程下的Python解释器。 3.单击下图中添加“”&#xff0c; 4.单击下图中的“Manage Repositories”按钮&#xff0c; 6.目前国内靠谱的 pip 镜像源有&#xff1a; - 清华&#xff1…

Java14来了!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!

Java 14 在 2020.3.17 日发布正式版了&#xff0c;但现在很多公司还在使用 Java 7 或 Java 8&#xff0c;每当看到 Java 又发布新版本心里就慌得一匹。不过此版本并不是 LTS (长期支持版) 版本&#xff0c;所以不要慌&#xff0c;我们先来了解一下好了&#xff0c;等 LTS 版本发…

在线批量压缩JPG图片-JpegMini

2019独角兽企业重金招聘Python工程师标准>>> 之前有推荐过一个在线批量压缩PNG图片的网站TinyPng&#xff0c;这儿小觉再次推荐一个同类网站&#xff0c;专门在线批量压缩JPG图片的JpegMini。 当然&#xff0c;大家或者会说现在很多工具或者网站都有提供在线批量压缩…

《大厂内部资料》Redis 性能优化的 13 条军规!全网首发

这是我的第 43 篇原创文章。Redis 是基于单线程模型实现的&#xff0c;也就是 Redis 是使用一个线程来处理所有的客户端请求的&#xff0c;尽管 Redis 使用了非阻塞式 IO&#xff0c;并且对各种命令都做了优化&#xff08;大部分命令操作时间复杂度都是 O(1)&#xff09;&#…

Redis 性能优化的 13 条军规!史上最全

Redis 是基于单线程模型实现的&#xff0c;也就是 Redis 是使用一个线程来处理所有的客户端请求的&#xff0c;尽管 Redis 使用了非阻塞式 IO&#xff0c;并且对各种命令都做了优化&#xff08;大部分命令操作时间复杂度都是 O(1)&#xff09;&#xff0c;但由于 Redis 是单线程…

Linux Debian11 Podman容器常用命令

Podman 是一个开源的容器运行时项目&#xff0c;可在大多数 Linux 平台上使用。Podman 提供与 Docker 非常相似的功能。它不需要在你的系统上运行任何守护进程&#xff0c;并且它也可以在没有 root 权限的情况下运行。 Podman 可以管理和运行任何符合 OCI&#xff08;Open Con…

惊呆了,竟然可以用这种方式秒建Redis集群?

前面我们讲了《Redis 性能优化的 13 条军规&#xff01;》&#xff0c;其中最重要的一条就是使用 Redis 的集群功能&#xff0c;那么本文我们就来看看&#xff0c;如何用 1s 钟的时间来创建一个 Redis 集群。 Redis Cluster 是 Redis 3.0 版本推出的 Redis 集群方案&#xff0…

华为交换机S5735S-L24T4S-QA2无法telnet远程访问

以前都是按照https://datutu.blog.csdn.net/article/details/106810113方法配置不同网段通过静态路由实现互通,华为S5700交换机开启telnet远程指定IP登陆配置(强烈推荐),现在新买的华为数通智选交换机S5735S-L24T4S-QA2 也是按照这步骤配置,令人不解的是,竟然无法telnet访…

99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!|建议收藏!!!...

世界上只有一种英雄主义&#xff0c;就是看清生活的真相之后依然热爱生活。对于 Lombok 我相信大部分人都不陌生&#xff0c;但对于它的实现原理以及缺点却鲜为人知&#xff0c;而本文将会从 Lombok 的原理出发&#xff0c;手撸一个简易版的 Lombok&#xff0c;让你理解这个热门…

PyQt5中多线程模块QThread解决界面卡顿无响应问题,线程池ThreadPoolExecutor解决多任务耗时操作问题

使用多线程模块QThread可以解决PyQt5界面程序执行比较耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题。 下面例子演示: 单击“运行”按钮,启动主线程,在子线程中采用线程池ThreadPoolExecuto解决ping多个IP耗时问题。在主界面把已ping过的IP添加到显示框…

干货!操作系统基础知识汇总!转给要面试的同学吧

作者&#xff1a;Guide哥来源&#xff1a;公众号 JavaGuide很多读者抱怨计算操作系统的知识点比较繁杂&#xff0c;自己也没有多少耐心去看&#xff0c;但是面试的时候又经常会遇到。所以&#xff0c;我带着我整理好的操作系统的常见问题来啦&#xff01;这篇文章总结了一些我觉…

eclipse的tomcat运行mave web项目

2019独角兽企业重金招聘Python工程师标准>>> 在eclipse中配置maven 安装成功后&#xff0c;就会多出Maven选项&#xff0c;在Installations中添加下载的Maven 在User Settings中配置本机的默认仓库 使用Eclipse的maven构建一个web项目 1、选择建立Maven Project 选…

Python在shell终端中显示进度条

Python在shell终端中显示进度条简单代码&#xff1a; from time import sleep from tqdm import tqdmmax 100 for i in tqdm(range(max)):sleep(0.1)效果如下&#xff1a;

没用过这些IDEA插件?怪不得写代码头疼

小伙伴们&#xff0c;大家好。今天准备和大家一起分享一下实际工作中常用的几款能提升幸福感和工作效率的IDEA插件吧&#xff0c;也欢迎小伙伴们在评论区安利出你们用过的觉得非常不错的插件&#xff0c;大家一起交流进步。1、Background Image Plus默认IDEA的背景一般都比较单…