ThreadLocalRandom源码解析
Random类通过以下逻辑获取下一个随机数,以获取下一个整数为例:
1 | public int nextInt(int bound) { |
由此可见,新的随机数生成需要两个步骤:
- 首先根据老的种子生成新的种子;
- 接着根据新的种子来计算新的随机数
在单线程情况下,每次调用nextInt都是根据老的种子计算出新的种子,这样可以保证随机数产生的随机性。但是在多线程下多个线程可能拿着同一个老种子去计算出新的种子,这样会导致每个线程拿到一样的新种子,间接导致所有线程拿到了完全一样的新的随机数。
因此,Random类使用了一个原子变量来保证第一个线程更新成功之后,其他的更新线程要丢弃自己的种子。Random在next(int bits)方法里面实现了这个逻辑。
1 | protected int next(int 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 | /** |
localInit
1 | /** |
首先计算根据 probeGenerator 计算当前线程中 threadLocalRandomProbe 的初始化值,然后根据 seeder 计算当前线程的初始化种子,然后把这两个变量设置到当前线程。
最后返回 ThreadLocalRandom 的实例,需要注意的是这个方法是静态方法,多个线程返回的是同一个 ThreadLocalRandom 实例。
nextInt(int bound)
计算当前线程的下一个随机数
1 | /** |
重点看下 nextSeed() 方法
1 | final long nextSeed() { |
如上代码首先使用 r = UNSAFE.getLong(t, SEED) 获取当前线程中 threadLocalRandomSeed 变量的值,然后在种子的基础上累加 GAMMA 值作为新种子,然后使用 UNSAFE 的 putLong 方法把新种子放入当前线程的 threadLocalRandomSeed 变量。