java并发包中ThreadLocalRandom类原理剖析
该ilei是JDK7在JUC包下面新增的随机数生成器吗,弥补了Random类在多线程下的缺陷
Random类及其局限性
public class RandomTest {
public static void main(String[] args) {
Random random=new Random();
for (int i=0;i<10;i++){
System.out.println(random.nextInt(5));
}
}
}
每个Random实例里面都有一个原子性的种子变量来记录当前的种子值,当要生成的随机数需要根据当前种子计算新的种子并更新回原子变量,在多线程下使用单个Random实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以造成大量线程进行自旋操作,这会降低并发性能
ThreadLocalRandom
public class ThreadLocalRandomTest {
public static void main(String[] args) {
ThreadLocalRandom random=ThreadLocalRandom.current();
for (int i=0;i<10;i++){
System.out.println(random.nextInt(5));
}
}
}
每个线程都维护一个种子变量,则每个变量生成随机数时候根据自己老的种子计算新的种子,并使用新种子更新老的种子,再根据种子计算随机数,就不会存在竞争问题,大大提高了高并发性能
源码分析
从图中可以看出ThreadLocalRandom类继承了Random 类并重写了nextlnt方法、在 ThreadLocalRandom 类中并没有使用继承自Random 类的原子性种子变量ThreadLocalRandom 中并没有存放具体的种子,具体的种子存放在具体的调用线程threadLocalRandomSeed 变量里面。ThreadLocalRandom 类似于 ThreadLocal 类,就是工具类。当线程调用 ThreadLocalRandom 的current 方法时,ThreadLocalRandom 负责初始调用线程的threadLocalRandomSeed 变量,也就是初始化种子。
当调用ThreadLocalRandom 的 nextInt方法时,实际上是获取当前线程threadLocalRandomSeed 变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed 变量,而后再根据新种子并使用具体算法计算随机数。这需要注意的是,threadLocalRandomSeed 变量就是 Thread 类里面的一个普通 long变量,它并不是原子性变量。其实道理很简单,因为这个变量是线程级别的,所以根本不需要使用
原子性变量,如果你还是不理解可以思考下 ThreadLocal 的原理其中 seeder 和 probeGenerator是两个原子性变量,在初始化调用线程的种子和探针变量时会用到它们,每个线程只会使用一次。
另外,变量instance 是 ThreadLocalRandom 的一个实例,该变量是 static 的。当多线程通过 ThreadLocalRandom 的 current 方法获取ThreadLocalRandom 的实例时,其实获取的是同一个实例。但是由于具体的种子是存放在线程里面的,所以在 ThreadLocalRandom的实例里面只包含与线程无关的通用算法,所以它是线程安全的
Unsafe机制
ThreadLocalRandom 的current()方法
该方法是获取ThreadLocalRandom实例,并初始化调用线程中的ThreadLocalRandomSeed和ThreadLocalRandomProbe变量
static final ThreadLocalRandom instance = new ThreadLocalRandom();
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);
}
int nextInt(int bound)方法
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 { // reject over-represented candidates
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
return r;
}
final long nextSeed(){
Thread t;
long r;
UNSAFE.putLong(t=Thread.currentThread(),SEED,r=UNSAFE.getLong(t,SEED)+GAMMA);
return r;
}
总结
首先讲解了Randon实现原理以及Random在多线程下需要竞争种子原子变量更新操作的缺点,从而引进ThreadLocalRandom类,该类使用ThreadLocal的原理,让每一个线程都持有一个本地的种子变量,该种子变量只有在使用随机数时候才会被初始化,在多线程下计算新种子是根据自己线程内维护的种子变量来进行更新,从而避免了竞争