Random的使用
Random random = new Random();
for (int i=0;i<10;i ){
System.out.println(random.nextInt(10));
}
那么Random有什么缺点呢?我们先看nextInt的源码?
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
//获取当前原子变量种子的值
//(6)
oldseed = seed.get();
//根据当前种子值计算新的种子
//(7)
nextseed = (oldseed * multiplier addend) & mask;
//(8)
//使用CAS 操作,它使用新的种子去更新老的种子,在多线程环境下 可能多个线程都同时
} while (!seed.compareAndSet(oldseed, nextseed));
//(9)
return (int)(nextseed >>> (48 - bits));
}
(6) 获取当前的原子变量种子的值
(7) 根据当前的种子值计算新的种子值
(8) 使用CAS 操作去更新旧的种子,在多线程的环境下可能会有多个线程同时执行(6)代码,就有可能多线程拿到的是同一个值,然后多个线程再同时执行(7),又得到同一个最新的种子,但是步骤(8)的CAS操作会保证只有一个线程可以更新老的种子为新的,那么失败的线程怎么办呢?失败的线程会通过循环获取更新后的种子作为当前的 种子去计算新的种子,这就保证了随机数的随机性。
代码(9) 使用固定算法根据新的种子计算随机数。
总结:
在每个Random实例里面都有一个原子性的变量用来记录当前 的种子性,当要生成新的随机数 时需要根据当前种子计算新的种子并更新回原子变量。在多线程下使用单个Random实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这回降低并发性能,所以ThreadLocalRandom应运而生。
Random的缺点是多个线程会使用同一个原子性种子变量,从而导致原子变量更新的竞争。
怎么解决Random的缺点呢 ?
如果每个线程都维护一个种子变量,那么每个线程生成随机数时都是根据自己老的种子计算新的种子,再根据新的种子计算随机数,那么就不会存在竞争问题了,这会大大提高并发性能。ThreadLocalRandom原理如下图:
怎么使用呢?只需要使用下面代码就可以了
ThreadLocalRandom.current().nextX(...)}
ThreadLocalRandom.current() 代码含义是 返回当前实例,并初始化此线程的种子。
ThreadLocalRandom是单例的。
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
/** The common ThreadLocalRandom */
static final ThreadLocalRandom instance = new ThreadLocalRandom();
我们看下localInit()方法的实现:
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);
}
上面代码是给每个线程分配一个种子;
我们继续看next()方法
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;
}
如上代码中,首先使用 ` r = UNSAFE.getLong(t, SEED) GAMMA` 获取当前线程中的
threadLocalRandomSeed变量的值,然后在种子的基础上累加GAMMA值作为新种子,而后使用UNSAFE的putLong方法把新种子放入当前线程的threadLocalRandomSeed变量中。
总结:
`ThreadLocalRandom` 使用ThreadLocal的原理,让每个线程都持有一个本地的种子变量,该种子变量只有在使用随机数时才会被初始化。在多线程计算新种子时是根据自己线程内维护的种子变量进行更新,从而避免了竞争。
最后建议:
在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线程持有一个单独的 Random 实例
,