前言
本节介绍下Java中随机数生成的方式
一、Random
特点:
- 线程安全,虽然共享该实例是线程安全的,但会因竞争同一 seed导致的性能下降
- 使用cas保证线程安全
使用方法
Random random = new Random();
int randomInt = random.nextInt(10);// [0,10) 内的随机数
int randomInt2 = random.nextInt(); // 生成一个int值
基本原理
伪随机,生成的随机数有一定规律。
构造函数:
在构造方法当中,根据当前时间的种子生成了一个 AtomicLong 类型的 seed
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
private static final AtomicLong seedUniquifier
= new AtomicLong(8682522807148012L);
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);
}
}
nextInt 与 nextInt(int)
public int nextInt() {
return next(32); // 传入的 32,代指的是 Int 类型的位数。
}
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));// cas
// 根据需要的位数返回
return (int)(nextseed >>> (48 - bits));
}
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 首先获取 31 位的随机数,注意这里是 31 位,和上面 32 位不同,因为在 nextInt() 方法中可以获取到随机数可能是负数,而 nextInt(int bound) 规定只能获取到 [0,bound) 之前的随机数,也就意味着必须是正数,预留一位符号位,所以只获取了31位。(不要想着使用取绝对值这样操作,会导致性能下降)
int r = next(31);
int m = bound - 1;
if ((bound & m) == 0) // i.e., bound is a power of 2 如果 bound 是2的幂次方,可以直接将第一步获取的值乘以 bound 然后右移31位,解释一下:如果 bound 是4,那么乘以4其实就是左移2位,其实就是变成了33位,再右移31位的话,就又会变成2位,最后,2位 int 的范围其实就是 [0,4) 了。
r = (int)((bound * (long)r) >> 31);
else { // 如果不是 2 的幂,通过模运算进行处理。
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;
}
并发下,CAS效率不高可以在 JDK7 之前,需要编码保证每个线
程持有一个单独的 Random 实例。在 JDK1.7 之后,Java 提供了更好的解决方案 ThreadLocalRandom
二、ThreadLocalRandom
特点
- 线程安全 seed不是全局变量 而是Thread中的三个变量
使用方法
ThreadLocalRandom.current().nextInt();
ThreadLocalRandom.current().nextInt(10);
基本原理
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);
}
其中Thread类中有专门为ThreadLocalRandom专门服务的属性
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
threadLocalRandomSeed:ThreadLocalRandom 使用它来控制随机数种子。
threadLocalRandomProbe:ThreadLocalRandom 使用它来控制初始化。
threadLocalRandomSecondarySeed:二级种子。
使用@sun.misc.Contended注解处理伪共享问题
用于制造随机数的方法nextInt()
public int nextInt() {
return mix32(nextSeed());
}
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
// 可以看见由于我们每个线程各自都维护了种子,这个时候并不需要 CAS,直接进行 put,在这里利用线程之间隔离,减少了并发冲突;相比较 ThreadLocal<Random>,ThreadLocalRandom 不仅仅减少了对象维护的成本,其内部实现也更轻量级。
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
ThreadLocalRandom 切记不要调用 current 方法之后,作为共享变量使用。每次直接使用就行了。
public class Test {
// seed 是维护在线程中的,ThreadLocalRandom只是作为一个计算工具
ThreadLocalRandom wrongRandom = new ThreadLocalRandom().current();
public int test(){
return wrongRandom.nextInt();
}
}