JAVA中生成随机数Random VS ThreadLocalRandom性能比较

简介: JAVA中生成随机数Random VS ThreadLocalRandom性能比较

前言


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


Random的使用


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

1671204032016.jpg

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

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


实现原理


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

1671204040959.jpg

  • 可以看到在构造方法中,根据当前时间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

1671204054174.jpg


实现原理


我们先来看下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();
   }
}

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

1671204071480.jpg

1671204076229.jpg

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


总结


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

目录
相关文章
|
10月前
|
Java 测试技术 API
Java Stream API:被低估的性能陷阱与优化技巧
Java Stream API:被低估的性能陷阱与优化技巧
602 114
|
12月前
|
机器学习/深度学习 Java 编译器
解锁硬件潜能:Java向量化计算,性能飙升W倍!
编译优化中的机器相关优化主要包括指令选择、寄存器分配、窥孔优化等,发生在编译后端,需考虑目标平台的指令集、寄存器、SIMD支持等硬件特性。向量化计算利用SIMD技术,实现数据级并行,大幅提升性能,尤其适用于图像处理、机器学习等领域。Java通过自动向量化和显式向量API(JDK 22标准)支持该技术。
449 4
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
Cloud Native 前端开发 Java
WebAssembly 与 Java 结合的跨语言协作方案及性能提升策略研究
本文深入探讨了WebAssembly与Java的结合方式,介绍了编译Java为Wasm模块、在Java中运行Wasm、云原生集成等技术方案,并通过金融分析系统的应用实例展示了其高性能、低延迟、跨平台等优势。结合TeaVM、JWebAssembly、GraalVM、Wasmer Java等工具,帮助开发者提升应用性能与开发效率,适用于Web前端、服务器端及边缘计算等场景。
438 0
|
10月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
809 8
|
10月前
|
存储 缓存 Java
Java 12相比Java 11有哪些性能上的提升?
Java 12相比Java 11有哪些性能上的提升?
293 3
|
11月前
|
Java Spring
如何优化Java异步任务的性能?
本文介绍了Java中四种异步任务实现方式:基础Thread、线程池、CompletableFuture及虚拟线程。涵盖多场景代码示例,展示从简单异步到复杂流程编排的演进,适用于不同版本与业务需求,助你掌握高效并发编程实践。(239字)
490 6
|
XML Java 数据库连接
性能提升秘籍:如何高效使用Java连接池管理数据库连接
在Java应用中,数据库连接管理至关重要。随着访问量增加,频繁创建和关闭连接会影响性能。为此,Java连接池技术应运而生,如HikariCP。本文通过代码示例介绍如何引入HikariCP依赖、配置连接池参数及使用连接池高效管理数据库连接,提升系统性能。
337 5
|
11月前
|
缓存 Java 开发者
Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛
本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。
452 3