Random 的线程安全实现方式
生成随机数大致需要两个步骤:
- 首先用老的种子生成一个新的种子。
- 然后用新的种子,计算生成随机数。
因为第二步算法是固定的,相同的种子生成相同的随机数。在多线程环境下,有可能有多个线程都拿同一个老种子去生成随机数,产生相同的值。
要想实现 Random 的线程安全,需要保证多个线程用同一老种子生成新种子时,如果有一个线程先生成了,那么其他线程需要丢弃老种子,用第一个线程生成的新种子来计算自己的新种子,一次类推。
原生的 Random 类是通过原子变量 + CAS 算法 + 自旋实现的。
// Random.java 部分代码
public
class Random implements java.io.Serializable {
// 原子的种子变量
private final AtomicLong seed;
// 构造方法
public Random(long seed) {
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
// subclass might have overriden setSeed
this.seed = new AtomicLong();
setSeed(seed);
}
}
// 生成随机数
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
// 使用 CAS + 自旋更新种子
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
}
上面的的实现,存在一个性能问题,由于种子是的更新是通过 CAS 算法实现的,多个线程同时更新只会有一个成功,其他线程会重试,如果并发量大的话,会有大量线程自旋重试。
ThreadLocalRandom 的线程安全实现方式
Random 的问题在于更新原子变量的竞争导致了多线程高并发情况的的性能缺陷。ThreadLocalRandom 通过让每个线程都持有一个种子,因为线程和种子是一一对应的,就不用对更新种子的操作进行同步,也就不会有竞争问题了。这样大大提高了并发性能。
public class ThreadLocalRandom extends Random {
private static final AtomicInteger probeGenerator = new AtomicInteger();
private static final AtomicLong seeder = new AtomicLong(initialSeed());
// 种子是在是存放在线程中的,ThreadLocalRandom 的实例只有算法,所以即使是同一个实例也没问题
static final ThreadLocalRandom instance = new ThreadLocalRandom();
private ThreadLocalRandom() {
initialized = true; // false during super() call
}
// 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);
}
// 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);
}
}
// 获取,并更新种子
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
// 先用 UNSAFE.getLong(t, SEED) 获取当前线程中的 seed
// 然后再用 seed + GAMMA 作为新种子
// 再使用 UNSAFE.putLong() 更新
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
}