ThreadLocalRandom类:JDK 1.7在JUC包下新增的随机数生成器,弥补了Random类在多线程下的缺陷
一、Random类及其局限性
使用:
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
Random random = new Random();
// 返回0~4的随机数
int i = random.nextInt(5);
}
}
原理:
随机数生成需要一个默认的种子,这个种子是一个long类型的数字,可以再创建Random对象时通过构造函数指定,如:new Random(47);
随机数生成大致分两个步骤:
- 根据旧种子计算出新种子
- 用新种子计算出随机数
在多线程下可能多个线程拿到同一个老的种子,去执行步骤(1),结果是多个线程产生的新种子是一样的,由于步骤(2)的算法是固定的,所以产生的随机数也一样,这并不是我们想要的
Random类在计算新种子的时候使用CAS操作保证只有一个线程可以更新老的种子,失败的线程会通过循环重新获取更新后的种子去计算下一个种子,保证了随机性
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));
}
局限性:
当多个线程同时操作同一个Random类计算随机数时,计算新种子的过程需要竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,降低并发性能
二、ThreadLocalRandom
使用:
import java.util.concurrent.ThreadLocalRandom;
public class RandomTest {
public static void main(String[] args) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int i = random.nextInt(5);
}
}
原理:
类似ThreadLocal类,每个线程维护一个threadLocalRandomSeed变量,调用current()方法会初始化线程的threadLocalRandomSeed变量
实现逻辑:
1.Unsafe机制
// Unsafe mechanics
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 = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}
2.ThreadLocalRandom.current()方法
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);
}
3.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; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
小结
Random在多线程下需要竞争种子原子变量更新操作存在缺点,因此引出ThreadLocalRandom类。ThreadLocalRandom使用ThreadLocal的原理,让每个线程都持有一个本地的种子变量,该种子变量只有在使用随机数才会被初始化。在多线程下计算新种子时是根据自己线程内维护的种子变量进行更新,从而避免了竞争。