写点什么

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

作者:JAVA旭阳
  • 2022-12-14
    浙江
  • 本文字数:2013 字

    阅读完需:约 7 分钟

前言

大家项目中如果有生成随机数的需求,我想大多都会选择使用 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 旭阳

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

发布于: 刚刚阅读数: 4
用户头像

JAVA旭阳

关注

欢迎关注个人公众号—— JAVA旭阳 2018-07-18 加入

旭阳,希望自己能像初升的太阳一样,对任何事情充满希望~~

评论

发布
暂无评论
JAVA中生成随机数Random VS ThreadLocalRandom性能比较_Java_JAVA旭阳_InfoQ写作社区