前语

我们项目中假如有生成随机数的需求,我想大多都会挑选运用Random来完成,它内部运用了CAS来完成。 实际上,JDK1.7之后,供给了别的一个生成随机数的类ThreadLocalRandom,那么他们二者之间的功能是怎么样的呢?

Random的运用

Random类是JDK供给的生成随机数的类, 这个类不是随机的,而是伪随机的。什么是伪随机呢? 伪随机是指生成的随机数是有一定规则的,这个规则呈现的周期因伪随机算法的优劣而异。 一般来说,周期比较长,但能够预见。 咱们能够经过以下代码简略地运用 Random:

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

Random中有许多办法。 这儿咱们就分析比较常见的nextInt()nextInt(int bound)办法。

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

完成原理

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

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

  • 能够看到在结构办法中,依据当时时间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

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

完成原理

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

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

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

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

显而易见,不管线程数量是多少,ThreadLocalRandom功能是远高于Random

总结

本文讲解了JDK中供给的两种生成随机数的方式,一个是JDK 1.0引进的Random类,别的一个是JDK1.7引进的ThreadLocalRandom类,因为底层的完成机制不同,ThreadLocalRandom的功能是远高于Random,建议后面我们在技能选型的时分优先运用ThreadLocalRandom

假如本文对你有协助的话,请留下一个赞吧
欢迎重视个人公众号——JAVA旭阳

本文正在参与「金石方案 . 瓜分6万现金大奖」