一、为什么要用ThreadLocalRandom?Random不够用吗?

咱们对Random可能比拟相熟,随机数生成的罕用类。来回顾下Random的用法:

Random random = new Random();// 输入一个0~5的随机数(包含0,不包含5)System.out.println(random.nextInt(5));

来看下nextInt的源码:

public int nextInt(int bound) {    if (bound <= 0)        throw new IllegalArgumentException(BadBound);    // 1.依据老的种子生成新的种子    int r = next(31);    int m = bound - 1;    // 2.依据新的种子计算随机数    if ((bound & m) == 0)  // i.e., bound is a power of 2        r = (int)((bound * (long)r) >> 31);    else {           for (int u = r;                u - (r = u % bound) + m < 0;                u = next(31))            ;    }    return r;}

再来看下依据老的种子生成新种子的代码:

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是用AtomicLong类型保留的,它是线程平安的,也就是说多个线程用CAS操作更新种子的时候,同一时刻只有一个线程更新胜利,其余线程会循环重试。这尽管保障了线程平安,但高并发时,会有大量线程在不停地自璇重试,这无疑升高了性能。所以,ThreadLocalRandom应运而生。

二、ThreadLocalRandom的实现原理

在探索实现原理前,先看看它的用法:

ThreadLocalRandom random = ThreadLocalRandom.current();random.next(5);

先来看看它的类构造:

public class ThreadLocalRandom extends Random {}

能够看到,它继承了Random,但跟ThreadLocal是怎么分割上的呢?
咱们能够猜测下,ThreadLocal是多个线程领有本人的变量正本,那么ThreadLocalRandom是不是也是这个思路,多个线程都领有本人的seed种子变量呢?这样就不必竞争同一个seed,从而晋升性能。
往下持续看源码,来证实咱们的猜测:
先看下它的重要属性

private static final sun.misc.Unsafe UNSAFE;private static final long SEED;private static final long PROBE;private static final long SECONDARY;static {    try {        UNSAFE = sun.misc.Unsafe.getUnsafe();        Class<?> tk = Thread.class;        SEED = UNSAFE.objectFieldOffset                   (tk.getDeclaredField("threadLocalRandomSeed"));        PROBE = UNSAFE.objectFieldOffset            (tk.getDeclaredField("threadLocalRandomProbe"));        SECONDARY = UNSAFE.objectFieldOffset            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));     } catch (Exception e) {        throw new Error(e);     }}

能够看到,SEED、PROBE、SECONDARY三个属性值都是Thread类里相应属性的偏移量。前面会剖析它们的作用。
再来看看ThreadLocalRandom.curren()办法:

public static ThreadLocalRandom current() {    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)        // 初始化        localInit();    // instance是饿汉式的单例    // static final ThreadLocalRandom instance = new ThreadLocalRandom();    return instance;}

来看下localInit()代码:

static final void localInit() {    int p = probeGenerator.addAndGet(PROBE_INCREMENT);    // 初始化probe值,跳过0    int probe = (p == 0) ? 1 : p; // skip 0    // 初始seed值    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));    Thread t = Thread.currentThread();    // 将probe和seed值设置到以后线程实例t中    UNSAFE.putLong(t, SEED, seed);    UNSAFE.putInt(t, PROBE, probe);}

再来看看nextInt()办法:

public int nextInt(int bound) {    if (bound <= 0)        throw new IllegalArgumentException(BadBound);    // 1.依据老种子计算新种子        int r = mix32(nextSeed());    // 2.依据新种子计算随机数    int m = bound - 1;    if ((bound & m) == 0) // power of two       r &= m;    else { // reject over-represented candidates       for (int u = r >>> 1;            u + m - (r = u % bound) < 0;            u = mix32(nextSeed()) >>> 1)            ;    }    return r;}

能够看到,上述步骤和Random类似,要害在nextSeed()外面:

final long nextSeed() {    Thread t; long r; // read and update per-thread seed    // 获取以后线程t的seed旧值,减少GAMMA值后,批改回以后线程t,返回新种子的值r    UNSAFE.putLong(t = Thread.currentThread(), SEED,    r = UNSAFE.getLong(t, SEED) + GAMMA);    return r;}

能够看到,每个线程操作的种子都是本人线程绑定的threadLocalRandomSeed,不会和其余线程产生竞争,因而晋升了性能。

三、总结

最初提下SEED、PROBE、SECONDARY三个属性值的作用。
Thread类外面有这3个属性的简略正文:

/** 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;

SEED很显然:就是本文说的随机数种子。
PROBE:非0的long类型值。翻译过去是线程探针,在本文如同没有施展重要作用,然而在其余类外面,比方LongAdder、ConcurrentHashMap外面都会用到这个probe,这个探针的作用是哈希线程,将线程和数组中的不同元素对应起来,尽量避免线程争用同一数组元素。能够翻看我的另一篇文章对于LongAdder的源码剖析。
SECONDARY:翻译过去是第二种子,在ConcurrentSkipListMap外面
会用到,之后的文章外面会剖析它的作用,请继续关注^_^