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对象,不存在线程之间的资源竞争,因此速度更快。