ThreadLocalRandom介绍
ThreadLocalRandom是JDK1.7以后提供出来的一个随机数生成工具类,性能比传统的Math.random()更高。
性能比较
ThreadLocalRandom和Math.random()的性能比较,测试步骤如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
long startTime = System.currentTimeMillis();
//Math.random() 开启10个线程
for (int i = 0; i < 10 ; i++){
Thread thread = new Thread(() -> {
// 循环10000次
for (int j = 0; j < 10000 ; j++) {
int x = (int) Math.random();
}
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
System.out.println("Math.random() 耗时 = " + (System.currentTimeMillis()-startTime));
long startTimeLocal = System.currentTimeMillis();
// ThreadLocalRandom 开启10个线程
for (int i = 0; i < 10 ; i++){
Thread thread = new Thread(() -> {
for (int j = 0; j < 10000 ; j++) {
int x = ThreadLocalRandom.current().nextInt();
}
});
thread.start();
}
System.out.println("ThreadLocalRandom 耗时 = " + (System.currentTimeMillis()-startTimeLocal));
}
}
测试结果如下:
第一次运行:
Math.random() 耗时 = 112
ThreadLocalRandom 耗时 = 7
第二次运行:
Math.random() 耗时 = 115
ThreadLocalRandom 耗时 = 15
第三次运行:
Math.random() 耗时 = 94
ThreadLocalRandom 耗时 = 9
第四次运行:
Math.random() 耗时 = 126
ThreadLocalRandom 耗时 = 16
通过上面的总结,我们可以很清晰看到,ThreadLocalRandom的性能优秀8-10倍
源码分析
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 等数据类型。
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对象,不存在线程之间的资源竞争,因此速度那是快的飞起。