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

上一篇 / 下一篇  2022-12-15 11:22:23

  有一份51Testing测试行业调查问卷需要您的助力,差不多三分钟的时间即可填完。我们给您准备了一份价值398元的测试课程作为礼品,感谢您的帮忙~链接:http://vote.51testing.com/
  
  前言
  大家项目中如果有生成随机数的需求,我想大多都会选择使用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(iteratinotallow=3, time = 5, timeUnit = TimeUnit.SECONDS)
  @Measurement(iteratinotallow=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。

TAG: 软件开发 Java java

 

评分:0

我来说两句

Open Toolbar