Java用于产生随机数的方法主要有两种:java.util.Random和java.util.concurrent.ThreadLocalRandom。
Random从Jdk 1.0开始就有了,而ThreadLocalRandom是Jdk1.7才新增的。简单从命名和类所在的包上看,两者的区别在于对并发的支持。
Random
Random是一个伪随机数生成器,它内置了一个种子数seed。
获取随机数的源码
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));
}
从源码可以看出,它内置了一个种子数seed,在每一次获取随机数执行next(int bits)时,按固定的算法更新seed值,从而达到伪随机的效果。
固定更新种子的算法为:
nextseed = (oldseed * multiplier + addend) & mask
换句话说,只要初始的种子是相同的,Random得到的随机数序列是相同的。
示例
Random random = new Random();
random.setSeed(1);
System.out.println(random.nextInt(10));
System.out.println(random.nextInt(10));
random.setSeed(1);
System.out.println(random.nextInt(10));
System.out.println(random.nextInt(10));
输出的结果为:
5
8
5
8
对于种子为1得到的随机序列均为5,8
Random线程安全的实现
JDK1.7新增ThreadLocalRandom是不是因为Random获取随机数不是线程安全的呢?
Random内置的种子使用了 AtomicLong类型,Random在执行next(int bits)获取种子,更新种子是线程安全的。所以在多线程使用同一个Random实例获取随机数是线程安全的。
问题也正是Random实现线程安全的方案上,在多线程并发环境下,各个线程在每一次产生随机数时都需要竞争获取种子。
ThreadLocalRandom
ThreadLocalRandom继承于Random,与Random不同的地方时,它提供了不一样的多线程并发实现方案。
ThreadLocalRandom使用了单例模式实现,使用ThreadLocalRandom.current()方法来获取ThreadLocalRandom实例。
current()方法
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
获取ThreadLocalRandom实例时,首先检查当前线程的PROBE的值是否为0,否则调用localInit
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);
}
localInit()方法做了这几件事:
- probeGenerator类型为AtomicInteger,它会产生一个非0的probe的值
- 种子产生器seeder类型为AtomicLong,它生成一个新的种子seed
- 对当前线程设置SEED和PROBE的值。
概括说,这段代码是对当前线程初始一个种子seed,使用非0值的probe来标记当前线程是否初始了种子。
也就是说,ThreadLocalRandom在获取实例时,给线程初始化了不同的种子。与Random不同的地方是,竞争种子只有在获取ThreadLocalRandom那一刻才会出现,在获取随机数的next()操作时是不会出现线程竞争。
public int nextInt() {
return mix32(nextSeed());
}
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;
}
种子是long类型,nextSeed在更新种子是在当前线程下,不会出现线程竞争。
setSeed()
Random是可以对实例初始种子,也可以使用setSeed()方法重置种子。而ThreadLocalRandom则不支持此操作:
public void setSeed(long seed) {
// only allow call from super() constructor
if (initialized)
throw new UnsupportedOperationException();
}
可以看到ThreadLocalRandom是禁止了setSeed()方法。
总结
Random和ThreadLocalRandom的最大区别在于对并发的处理上不同:
- Random是在每次获取随机数时需要竞争种子的更新
- ThreadLocalRandom则是在获取ThreadLocalRandom实例时才需要竞争种子的更新,大大降低种子更新竞争的频率。