一、为什么要用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里面
会用到,之后的文章里面会分析它的作用,请持续关注^_^