首先看下jdk中的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));
return (int)(nextseed >>> (48 - bits));
}
- 获取当前原子变量种子的值
- 根据当前种子值计算新的种子
- 使用CAS操作,使用新的种子去更新老的种子,多线程情况下可能多个线程都拿到的当前种子的值是同一个,然后计算的新种子也都是一样的,但是CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环从新获取更新后的种子作为当前种子去计算老的种子,这就保证了随机数的随机性。
- 使用固定算法根据新的种子计算随机数。
Random存在的问题
多线程下使用单个Random实例生成随机数的时候,多个线程同时计算随机数计算新的种子时候多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这是会降低并发性能的,所以ThreadLocalRandom应运而生。
JDK7中的ThreadLocalRandom
ThreadLocalRandom继承了Random并重写了nextInt方法,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的实例里面只是与线程无关的通用算法,所以是线程安全的。