Random类及其局限性
一般情况下,我们都会使用java.util.Random来生成随机数(Math.random()也是使用Random实例生成随机数)。
示例
public static void main(String[] args) {
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt(10));
}
}
分析
下面以nextInt(int bound) 方法为例来分析Random的源码
public int nextInt(int bound) {
//边界检测
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//获取下一随机数
int r = next(31);
//(*)此处以特定算法根据r计算出最终结果
...
return r;
}
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
//CAS操作更新seed
do {
oldseed = seed.get();
//根据老的种子计算新的种子
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
由此可见,
生成新的随机数
需要两步:
- 根据老的种子生成新的种子
- 由新的种子计算出新的随机数
单线程下每次调用nextInt都会根据老的种子计算出新的种子,可以保证随机性。
但多线程下,不同线程可能拿着同一个老的种子去计算新种子
,如果next方法因此返回相同的值的话,由于( * )处的算法是固定的,这会导致不同线程生成相同的随机数
,这并非我们想要的。所以next方法使用CAS操作保证每次只有一个线程可以更新老的种子,失败的线程则重新获取
,这样就解决了上述问题。
但这样处理仍有一个缺陷:当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新
操作,由于该操作为CAS操作,同时只有一个线程会成功,这样会造成大量的自旋重试
,导致并发性能降低。而ThreadLocalRandom可以完美解决此问题。
ThreadLocalRandom
每个线程新增种子变量threadLocalRandomSeed,每个线程根据老的种子变量变成新的种子。
ThreadLocalRandom类继承了Random类并重写了nextInt方法,在ThreadLocalRandom没有直接继承Random类的原子性种子变量。
ThreadLocalRandom没有存放具体的种子,具体的种子放在具体调用线程的threadLocalRandomSeed变量里。
ThreadLocalRandom类似在ThreadLocal类,是个工具类。当线程调用ThreadLocalRandom的current方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子。
当调用ThreadLocalRandom的nextInt方法时,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed变量,然后再根据新种子并使用具体算法计算随机数。
threadLocalRandomSeed变量就是Thread里的一个普通long变量,不是原子性变量。
seeder和probeGenerator是两个原子性变量,在初始化调用线程种子和探针变量时会用到它们,每个线程只会使用一次。
变量instance是ThreadLocalRandom的一个实例,变量是static的。
当多线程通过ThreadLocalRandom的current方法获取ThreadLocalRandom实例时,其实获取的是一个实例。具体种子存放在线程里,所有ThreadLocalRandom实例里只包含与线程无关的通用算法,所以是线程安全的。
示例
public static void main(String[] args) {
Random random = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt(10));
}
}
原理
Random的缺点在于
多个线程会使用同一个原子性变量
,从而导致对原子变量的竞争;而ThreadLocalRandom保证每个线程都维护一个种子变量,每个线程根据自己老的种子生成新的种子
,避免了竞争问题,大大提高了并发性能。
源码分析
Unsafe机制
// Unsafe机制
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
// 获取unsafe实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// 获取Thread类里threadLocalRandomSeed变量在Thread的偏移量
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// 获取Thread类里threadLocalRandomProbe变量在Thread的偏移量
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// 获取Thread类里threadLocalRandomSecondarySeed变量在Thread的偏移量,这个值在LongAdder中会用到
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}
ThreadLocalRandom current()方法
// ThreadLocalRandom current()方法
// 获取ThreadLocalRandom实例,并初始化调用线程中的threadLocalRandomSeed和threadLocalRandomProb变量
static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
//检测是否初始化过
//PROBE为Thread类中threadLocalRandomProb偏移
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)// 说明当前线程是第一次调用ThreadLocalRandom的current方法
localInit();// 计算当前线程的初始化种子变量 目的为了延迟初始化,在不需要使用随机数功能时就不初始化Thread类中的种子变量,是一种优化
return instance;
}
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);// 计算当前线程中threadLocalRandomProb的初始化值
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));// 根据sedder计算当前线程的初始化种子
Thread t = Thread.currentThread();
//SEED为Thread类中threadLocalRandomSeed内存偏移
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
如果线程中第一次调用current()方法
,则调用localInit()进行初始化
设置当前线程中的threadLocalRandomProb和threadLocalRandomSeed变量
。
这个是静态方法多次调用的是同一个ThreadLocalRandom实例。
下面来看int nextInt(int bound)方法
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//根据当前Thread中的threadLocalRandomSeed变量生成新种子
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;
}
final long nextSeed() {
Thread t; long r;
//生成并存入新种子
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
如上,首先调用nextSeed()根据当前Thread中的threadLocalRandomSeed变量生成并存入新种子,然后经过特定算法得出了nextInt的值。
首先使用 r = UNSAFE.getLong(t, SEED) 获取当前线程中threadLocalRandomSeed变量的值,然后在种子的基础上累加GAMMA值作为新种子,而后使用UNSAFE.putLong方法把心种子放入当前线程的threadLocalRandomSeed变量中。