ThreadLocalRandom和Random
ThreadLocalRandom是JDK1.7以后提供出来的一个随机数生成工具类,性能比传统的Math.random()更高,ThreadLocalRandom的性能优秀8-10倍。
直接看源码,ThreadLocalRandom 继承于 Random
public static ThreadLocalRandom current() {
// 调用unsafe类里面通过这个方法是从当前对象中获取偏移offset的值
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
// 等于0 ,说明当前线程没有初始化,调用初始化方法。
localInit();
return instance;
}
/**
* 该方法,主要是将当前线程,seed对象,放入到MAP结构中,
*/
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);
}
nextInt方法
public int nextInt() {
return mix32(nextSeed());
}
/**
* 通过当前线程,拿到seed , 返回出去,用作随机数计算
*/
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;
}
随机数的一个核心参数就是seed值的 , seed就是随机算法中的种子 , 通过这个种子和一个未知的数"0xff51afd7ed558ccdL" 相乘,然后得到一个未知的数,根据不同的类型截取不同的长度,如int, long 等数据类型。
存在的问题
Thread就加了Unsafe的代码,随机规则依然是用的Random,也就是某种固定的算法,所以只要seek一样,随机就是一样的,seek不能被猜出来,所以default用的当前时间。
Math.random()
protected int next(int bits) {
long oldseed, nextseed;
// 多个线程获取到的是同一个seed对象
AtomicLong seed = this.seed;
do {
// 当前值
oldseed = seed.get();
// 下一个值通过某种计算出来
nextseed = (oldseed * multiplier + addend) & mask;
// 通过CAS操作去更新,保证更新正确
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
原始的实现是所有的线程共享一个seed对象,虽然用的是CAS去更新,但是do while里面还是通过循环是重试,在高并发场景下是很耗费资源的。
ThreadLocalRandom是通过每个线程获取自己的seed对象,不存在线程之间的资源竞争,因此速度更快。