阿里标签识别教程(阿里开发手册为什么不建议使用Random)(1)

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)(2)

怎么解决Random的缺点呢 ?

如果每个线程都维护一个种子变量,那么每个线程生成随机数时都是根据自己老的种子计算新的种子,再根据新的种子计算随机数,那么就不会存在竞争问题了,这会大大提高并发性能。ThreadLocalRandom原理如下图:

阿里标签识别教程(阿里开发手册为什么不建议使用Random)(3)

怎么使用呢?只需要使用下面代码就可以了

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 实例

,