前言

大家项目中如果有生成随机数的需求,我想大多都会选择使用Random来实现,它内部使用了CAS来实现。 实际上,JDK1.7之后,提供了另外一个生成随机数的类ThreadLocalRandom,那么他们二者之间的性能是怎么样的呢?

Random的使用

Random类是JDK提供的生成随机数的类, 这个类不是随机的,而是伪随机的。什么是伪随机呢? 伪随机是指生成的随机数是有一定规律的,这个规律出现的周期因伪随机算法的优劣而异。 一般来说,周期比较长,但可以预见。 我们可以通过以下代码简单地使用 Random:

Random中有很多方法。 这里我们就分析比较常见的nextInt()nextInt(int bound)方法。

  • nextInt()会计算int范围内的随机数,
  • nextInt(int bound)会计算[0,bound) 之间的随机数,左闭右开。

实现原理

Random类的构造函数如下图所示:

  • 可以看到在构造方法中,根据当前时间seed生成了一个AtomicLong类型的seed
public int nextInt() {
return next(32);
}
  • 这里面直接调用了next()方法,传入了32,这里的32是指Int的位数。
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));
return (int)(nextseed >>> (48 - bits));
}
  • 这里会根据seed的当前值,通过一定的规则(伪随机)计算出下一个seed,然后进行CAS。 如果CAS失败,继续循环上述操作。 最后根据我们需要的位数返回。

小结:可以看出在next(int bits)方法中,对AtomicLong进行了CAS操作,如果失败则循环重试。 很多人一看到CAS,因为不需要加锁,第一时间就想到了高性能、高并发。 但是在这里,却成为了我们多线程并发性能的瓶颈。 可以想象,当我们有多个线程执行CAS时,只有一个线程一定会失败,其他的会继续循环执行CAS操作。 当并发线程较多时,性能就会下降。

ThreadLocalRandom的使用

JDK1.7之后,提供了一个新类ThreadLocalRandom来替代Random

实现原理

我们先来看下current()方法。

public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
  • 如果没有初始化,先进行初始化,这里我们的seed不再是全局变量了。 我们的线程中有三个变量:
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed; /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe; /** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
  • threadLocalRandomSeed:这是我们用来控制随机数的种子。
  • threadLocalRandomProbe:这个就是ThreadLocalRandom,用来控制初始化。
  • threadLocalRandomSecondarySeed:这是二级种子。

关键代码如下:

UNSAFE.putLong(t = Thread.currentThread(), SEED,r=UNSAFE.getLong(t, SEED) + GAMMA);

可以看出,由于每个线程都维护自己的seed,所以此时不需要CAS,直接进行put。 这里通过线程间的隔离来减少并发冲突,所以ThreadLocalRandom的性能非常高。

性能对比

通过基准工具JMH测试:

@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations=3, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations=3,time = 5)
@Threads(4)
@Fork(1)
@State(Scope.Benchmark)
public class Myclass {
Random random = new Random();
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); @Benchmark
public int measureRandom(){
return random.nextInt();
}
@Benchmark
public int threadLocalmeasureRandom(){
return threadLocalRandom.nextInt();
} }

运行结果如下图所示,最左边是并发线程的数量:

显而易见,无论线程数量是多少,ThreadLocalRandom性能是远高于Random

总结

本文讲解了JDK中提供的两种生成随机数的方式,一个是JDK 1.0引入的Random类,另外一个是JDK1.7引入的ThreadLocalRandom类,由于底层的实现机制不同,ThreadLocalRandom的性能是远高于Random,建议后面大家在技术选型的时候优先使用ThreadLocalRandom

如果本文对你有帮助的话,请留下一个赞吧

欢迎关注个人公众号——JAVA旭阳

更多学习资料请移步:程序员成神之路

JAVA中生成随机数Random VS ThreadLocalRandom性能比较的更多相关文章

  1. Java如何生成随机数 - Random、ThreadLocalRandom、SecureRandom

    Java7 的Random伪随机数和线程安全的ThreadLocalRandom 一.Random伪随机数: Random 类专门用于生成一个伪随机数,它有两个构造器: 一个构造器使用默认的种子(以当 ...

  2. java中的随机数Random

    java中一般有两种随机数,一个是Math中random()方法,一个是Random类. 一.Math.random()  :     随即生成0<x<1的小数 实例:如何写,生成随机生成 ...

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

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

  4. JAVA中生成指定位数随机数的方法总结

    JAVA中生成指定位数随机数的方法很多,下面列举几种比较常用的方法. 方法一.通过Math类 public static String getRandom1(int len) { int rs = ( ...

  5. 为什么说Java中的随机数都是伪随机数?

    什么是伪随机数?  1.伪随机数是看似随机实质是固定的周期性序列,也就是有规则的随机. 2.只要这个随机数是由确定算法生成的,那就是伪随机,只能通过不断算法优化,使你的随机数更接近随机.   (随机这 ...

  6. Java中产生随机数的两个方法

    Java中产生随机数的两个方法 一.利用random方法来生成Java随机数. 在Java语言中生成Java随机数相对来说比较简单,因为有一个现成的方法可以使用.在Math类中,Java语言提供了一个 ...

  7. java中生成不重复随机的数字

    Java中产生随机数 1 . 调用java.lang下面Math类中的random()方法产生随机数 新建一个文件后缀名为java的文件,文件名取为MyRandom,该类中编写如下的代码: publi ...

  8. java中生成验证码,以及验证码的使用

    java中生成验证码,以及验证码的使用: 1:验证码生成工具类: import java.awt.Color; import java.awt.Font; import java.awt.Graphi ...

  9. Java中利用随机数的猜拳游戏

    Java中利用随机数的猜拳游戏,实现非常简单,重难点在于随机数的产生. 首先GameJude类是用于判断输赢的一个类: package testGame; public class GameJudge ...

  10. 关于android开发添加菜单XML文件之后无法在R.java中生成ID的问题

    因为和同学分开做的android软件,现在想整合他做的界面部分,于是拷贝了res和src文件夹的文件,其中包括一个res.menu文件夹中的XML.但是每次将该文件导入到工程总无法自动在R.java中 ...

随机推荐

  1. git commit、git push、git pull、 git fetch、git merge 的含义与区别

    git commit:是将本地修改过的文件提交到本地库中: git push:是将本地库中的最新信息发送给远程库: git pull:是从远程获取最新版本到本地,并自动merge: git fetch ...

  2. Windows界面个人常用快捷键

    分享一下个人常用快捷键. 说明:字母排序规则遵循字母表(a->z) 快捷键 介绍 windows+d 由当前应用直接返回桌面,再按一次回到应用 windows+e 打开文件资源管理器 windo ...

  3. Python(二)常用的正则表达式

    表单字段验证常用正则表达式 1.姓名 /^[\u4e00-\u9fa5]{2,4}$/ //2-41 2.手机号码 /^(13[0-9]|14[01456879]|15[0-35-9] 3.座机号码 ...

  4. 记Windows的一个存在了十多年的bug

    bug Windows有一个bug,持续了十多年,从Windows Visita开始(2007年),一直存在,直到Windows11(2021年)才修复(其实也不叫修复,后面我再具体说),而Windo ...

  5. 洛谷P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G (tarjan缩点)

    在本题中很明显,给你一个有向图,要用tarjan缩点. 缩点后,一头牛要受到所有牛的欢迎,那么该点的出度要为0,这是容易证明的:如果该点还有出度,比如a连向b,那么a不受到b的欢迎.所以我们要找出度为 ...

  6. P2216 [HAOI2007]理想的正方形 方法记录

    [HAOI2007]理想的正方形 题目描述 有一个 \(a \times b\) 的整数组成的矩阵,现请你从中找出一个 \(n \times n\) 的正方形区域,使得该区域所有数中的最大值和最小值的 ...

  7. map集合类型/实体类类型的参数

    map集合类型的参数 若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合, 将这些数据放在map中 只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要 ...

  8. SQL语句编写的练习(MySQL)

    SQL语句编写的练习(MySQL) 一.建表 1.学生表(Student) 学号 | 姓名 | 性别 | 出生年月 | 所在班级 create table Student( sno varchar(2 ...

  9. 解决@Url.Action("Action", "Controller",new {p1=v1,p2=v2 })的传参问题

    1.首先@Url.Action("Action", "Controller",new {p1=v1,p2=v2 })后面的model参数不可以直接用变量 需要先 ...

  10. C#中的特性+反射

    反射 反射指程序可以访问.检测和修改它本身状态或行为的一种能力. 程序集包含模块,而模块包含类型,类型又包含成员.反射则提供了封装程序集.模块和类型的对象. 您可以使用反射动态地创建类型的实例,将类型 ...