ThreadLocalRandom源码解析

ThreadLocalRandom源码解析

Random类通过以下逻辑获取下一个随机数,以获取下一个整数为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int nextInt(int bound) {
// 参数检查
if (bound <= 0)
throw new IllegalArgumentException(BadBound);

// 根据老的种子生成新的种子
int r = next(31);
int m = bound - 1;
// 根据新的种子计算随机数
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;
}

由此可见,新的随机数生成需要两个步骤:

  1. 首先根据老的种子生成新的种子;
  2. 接着根据新的种子来计算新的随机数

在单线程情况下,每次调用nextInt都是根据老的种子计算出新的种子,这样可以保证随机数产生的随机性。但是在多线程下多个线程可能拿着同一个老种子去计算出新的种子,这样会导致每个线程拿到一样的新种子,间接导致所有线程拿到了完全一样的新的随机数。

因此,Random类使用了一个原子变量来保证第一个线程更新成功之后,其他的更新线程要丢弃自己的种子。Random在next(int bits)方法里面实现了这个逻辑。

1
2
3
4
5
6
7
8
9
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));
}

可以看到,在更新种子的第8行,使用了一个CAS操作,它保证只会有一个线程能更新种子成功,其余线程会失败,然后自旋,获取更新后的种子再作为当前的种子。

这种操作虽然保证了多个线程竞争时能保证随机性,但是只有一个线程会成功,其他线程要浪费大量时间在自旋操作上,这样会降低并发的性能。

ThreadLocalRandom类

为了弥补多线程高并发情况下Random的缺陷,在JUC包下新增了ThreadLocalRandom类。它的原理就是每个线程自己维护一个种子变量,生成随机数的时候根据自己老的种子计算新的种子,并使用新种子更新老的种子,再根据新种子计算随机数。

ThreadLocalRandom类继承了Random类并重写了nextInt方法,在ThreadLocalRandom类中并没有使用继承自Random类的原子性种子变量。在ThreadLocalRandom中并没有存放具体的种子,具体的种子存放在具体的调用线程的threadLocalRandomSeed变量里面。当线程调用ThreadLocalRandom的current方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子。

当调用ThreadLocalRandom的nextInt方法时,实际上是获取当前线程的ThreadLocalRandomSeed变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed变量,而后再根据新种子并使用具体算法计算随机数。

其中变量 seeder 和 probeGenerator 是两个原子性变量,在初始化调用线程的种子和探针变量时候用到,每个线程只会使用一次。

另外变量 instance 是个 ThreadLocalRandom 的一个实例,该变量是 static 的,当多线程通过 ThreadLocalRandom 的 current 方法获取 ThreadLocalRandom 的实例时候其实获取的是同一个,但是由于具体的种子是存放到线程里面的,

所以 ThreadLocalRandom 的实例里面只是与线程无关的通用算法,所以是线程安全的。

ThreadLocalRandom.current

1
2
3
4
5
6
7
8
9
10
11
/**
* Returns the current thread's {@code ThreadLocalRandom}.
*
* @return the current thread's {@code ThreadLocalRandom}
*/
public static ThreadLocalRandom current() {
// 如果当前线程中的threadLocalRandomProbe变量值为0,表示当前线程时第一次调用ThreadLocalRandom的current方法
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}

localInit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Initialize Thread fields for the current thread. Called only
* when Thread.threadLocalRandomProbe is zero, indicating that a
* thread local seed value needs to be generated. Note that even
* though the initialization is purely thread-local, we need to
* rely on (static) atomic generators to initialize the values.
*/
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);
}

首先计算根据 probeGenerator 计算当前线程中 threadLocalRandomProbe 的初始化值,然后根据 seeder 计算当前线程的初始化种子,然后把这两个变量设置到当前线程。

最后返回 ThreadLocalRandom 的实例,需要注意的是这个方法是静态方法,多个线程返回的是同一个 ThreadLocalRandom 实例。

nextInt(int bound)

计算当前线程的下一个随机数

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
/**
* Returns a pseudorandom {@code int} value between zero (inclusive)
* and the specified bound (exclusive).
*
* @param bound the upper bound (exclusive). Must be positive.
* @return a pseudorandom {@code int} value between zero
* (inclusive) and the bound (exclusive)
* @throws IllegalArgumentException if {@code bound} is not positive
*/
public int nextInt(int bound) {
// 参数校验
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 根据当前线程中的种子计算新种子
int r = mix32(nextSeed());
// 根据新种子和bound计算随机数
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;
}

重点看下 nextSeed() 方法

1
2
3
4
5
6
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}

如上代码首先使用 r = UNSAFE.getLong(t, SEED) 获取当前线程中 threadLocalRandomSeed 变量的值,然后在种子的基础上累加 GAMMA 值作为新种子,然后使用 UNSAFE 的 putLong 方法把新种子放入当前线程的 threadLocalRandomSeed 变量。

参考资料

Java并发编程之美

0%