ThreadLocalRondom原理剖析

ThreadLocalRondom是JDK 7在并发包中新增的随机数生成器,该类弥补了Random类在并发环境下的缺陷。

Random的局限性

Random生成随机数的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int nextInt(int bound) {
//参数校验
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//根据老的种子生成新的种子
int r = next(31);
int m = bound - 1;
//根据新种子计算随机数
if ((bound & m) == 0)
r = (int)((bound * (long)r) >> 31);
else {
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;
}

next方法通过计算生成新的种子。用原子变量来存放种子,多线程的情况下,CAS操作会保证只有一个线程可以更新老的种子为新种子,更新失败的线程进行自旋,这降低了并发性能,所以产生了ThreadLocalRandom。

1
2
3
4
5
6
7
8
9
10
11
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
//seed计算公式,通过CAS操作进行更新
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
//将得到的值进行逻辑右移
return (int)(nextseed >>> (48 - bits));
}

ThreadLocalRandom简介

ThreadLocalRandom和ThreadLocal的原理相似,ThreadLocalRandom使得每个线程都维护自己独有的种子变量,这样就不存在竞争问题,大大提高并发性能。

current方法

在current方法中,获得ThreadLocalRandom实例并初始化。seed不再是一个AtomicLong变量,在Thread类中有三个变量。

  • threadLocalRandomSeed:使用它来控制随机数种子。
  • threadLocalRandomProbe:使用它来控制初始化。
  • threadLocalRandomSecondarySeed:二级种子。

这三个变量都加了sun.misc.Contended注解,用来避免伪共享问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
   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);
}


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

Unsafe机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
//获取Thread类里面threadLocalRandomSeed变量在Thread实例的偏移量
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
//获取Thread类里面threadLocalRandomProbe变量在Thread实例的偏移量
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
//获取Thread类里面threadLocalRandomSecondarySeed变量在Thread实例的偏移量
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}

nextInt方法

nextInt方法用于获取下一个随机数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int nextInt(int bound) {
//参数校验
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//根据当前线程中的种子计算新种子
int r = mix32(nextSeed());
//根据新种子计算随机数
int m = bound - 1;
if ((bound & m) == 0) // power of two
r &= m;
else {
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
return r;
}

nextSeed方法

获取当前线程的threadLocalRandomSeed变量值,然后加上GAMMA值作为新种子。可参照上文Unsafe机制。

1
2
3
4
5
6
final long nextSeed() {
Thread t; long r;
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}

参考

《Java并发编程之美》

文章作者: L1nker4
文章链接: https://l1n.wang/2020/04/threadlocal-rondom/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 L1nker4's Blog