ThreadLocalRandom类原理剖析
Random的缺陷
原来的Random类的产生一个新随机数的树的步骤为:
- 首先根据老的种子生成新的种子。
- 然后根据新的种子来计算新的随机数。
代码:
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;
}
但是在多线程的情况下,多个线程可能都是拿到的同一个老的种子以计算出新的种子,产生的新的种子是一样的,显然就是不是我们所预期的。因此random函数使用一个原子变量达到了第一个线程拿到老的种子计算出新的种子后,第二个线程就要丢弃自己拿到的老的种子而使用第一个线程产生的新的种子作为老种子来计算出自己新的种子。保证在多个线程中产生的随机数值是随机的。
代码:
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));//使用CAS操作用新的种子更新老的种子,失败的线程会通过线程循环获取更新后的新种子当做当前种子去计算
return (int)(nextseed >>> (48 - bits));//使用固定算法根据新的种子计算随机数
}
多个线程竞争同一个原子变量的更新操作,由于是CAS操作,只会有一个成功,大部分失败的线程会进行自旋重试,降低并发性能,因此ThreadLocalRandom应运而生。
ThreadLocalRandom的原理
因为Random类的缺点是多个线程会使用同一个原子性种子变量,而ThreadLocalRandom使用ThreadLocalRandom.current()来获取当前线程的随机数生产器。原理是将之前的多个线程竞争同一个原子变量让每个线程都复制一份,每个线程生成随机数都是根据自己老的种子生成新的种子来计算出随机数。同ThreadLocal的原理相同。
ThreadLocalRandom使用
ThreadLocalRandom tlr = ThreadLocalRandom.current();
System.out.println(tlr.nextInt(10,50));//生成一个10~50之间的随机数