前言
Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。 Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。可以直接使用 ThreadLocalRandom 。
1.Random
Random 使用相同的 seed 创建了两个实例,并且对每个实例进行了相同的方法调用序列,则它们将生成并返回相同的数字序列。
Tip:
Random中的seed是一个Long类型的原子变量AtomicLong对象
使用如下:
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println("第" + i + "次:" + random.nextInt(10));
}
第0次:1
第1次:1
第2次:7
第3次:7
第4次:4
第5次:0
第6次:5
第7次:5
第8次:8
第9次:4
这里使用了Random的nextInt方法获取随机数,源码如下:
public int nextInt(int bound) {
// 验证边界的合法性
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 根据老种子生成新种子
int r = next(31);
// 计算最大值
int m = bound - 1;
// 根据新种子计算随机数
if ((bound & m) == 0) // i.e., bound is a power of 2
r = (int)((bound * (long)r) >> 31);
else {
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;
}
上面的源码中根据老种子生成新种子的实现方式如下
protected int next(int bits) {
// 得到Long类型的原子变量AtomicLong对象
AtomicLong seed = this.seed;
// 声明老种子和新种子变量
long oldseed;
long nextseed;
do {
// 获取原子变量种子的值
oldseed = seed.get();
// 根据当前种子计算出新种子的值
nextseed = oldseed * 25214903917L + 11L & 281474976710655L;
// 使用新的种子去更新老的种子
} while(!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> 48 - bits);
}
上面两段代码可以看出:
- 根据老种子生成新种子;
- 根据新种子计算出随机数。
通常使用时可直接使用 Math 类中的 random() 方法。
2.Math.random()
使用如下:
for (int i = 0; i < 10; i++) {
// Math.random()生成的是一个double类型的值,这里强转为int型输出
int random = (int) (Math.random() * 10);
System.out.println("第" + i + "次:" + random);
}
第0次:1
第1次:3
第2次:9
第3次:0
第4次:2
第5次:4
第6次:4
第7次:4
第8次:9
第9次:2
random()来源
public final class Math {
// 其余代码省略......
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
// 其余代码省略......
}
3.ThreadLocalRandom
使用如下:
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
System.out.println("第" + i + "次:" + threadLocalRandom.nextLong(10));
}
第0次:7
第1次:1
第2次:3
第3次:0
第4次:0
第5次:3
第6次:7
第7次:7
第8次:8
第9次:6
ThreadLocalRandom继承了Random并重写了nextInt方法,当调用ThreadLocalRandom的current方法时获取到ThreadLocalRandom的实例,如果线程第一次调用 current() 方法时就会执行 localInit()方法,localInit()中为线程初始化了 seed,并保存在 Unsafe 里,。实现如下:
public static ThreadLocalRandom current() {
// 如果线程第一次调用 current() 方法,执行 localInit()方法
if (U.getInt(Thread.currentThread(), PROBE) == 0) {
localInit();
}
return instance;
}
再来看看localInit()方法
static final void localInit() {
int p = probeGenerator.addAndGet(-1640531527);
int probe = p == 0 ? 1 : p;
long seed = mix64(seeder.getAndAdd(-4942790177534073029L));
Thread t = Thread.currentThread();
U.putLong(t, SEED, seed);
U.putInt(t, PROBE, probe);
}
ThreadLocalRandom中的nextInt方法如下:
public int nextInt(int bound) {
// 验证边界的合法性
if (bound <= 0) {
throw new IllegalArgumentException("bound must be positive");
} else {
// 根据当前线程中种子计算新种子
int r = mix32(this.nextSeed());
// 根据新种子和bound计算随机数
int m = bound - 1;
if ((bound & m) == 0) {
r &= m;
} else {
for(int u = r >>> 1; u + m - (r = u % bound) < 0; u = mix32(this.nextSeed()) >>> 1) {
}
}
return r;
}
}