JDK8新增ThreadLocalRandom类

Random使用

  //使用默认随机数生成器(会生成一个默认的值seedUniquifier() ^ System.nanoTime())
Random rand = new Random();
for(int i = 0; i < 10; i ++){
    System.out.println(rand.nextInt(5) );
}

和Random类的区别

为了解决多线程高并发下Random的缺陷,JUC包下新增了ThreadLocalRandom类。ThreadLocalRandom继承Random类。

如何生成随机数?

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;
}

在单线程情况下每次调用nextInt都是根据老的种子计算出来新的种子,这是可以保证随机数产生的随机性的。在多线程下多个线程可能都拿同一个老的种子去计算新的种子,这会导致多个线程产生的新种子是一样的,会导致多个线程产生相同的随机值。为了保证多线程下产生的随机数是随机的,在创建Random对象时候初始化的种子就保存到了种子原子变量里面

  protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        //获取当前原子变量种子的值
        oldseed = seed.get();
        //根据当前种子值计算新的种子
        nextseed = (oldseed * multiplier + addend) & mask;
        //CAS操作会保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环从新获取更新后的种子作为当前种子去计算老的种子,保证了随机数的随机性
    } while (!seed.compareAndSet(oldseed, nextseed));
   //使用固定算法根据新的种子计算随机数
    return (int)(nextseed >>> (48 - bits));
}

多个线程同时计算随机数计算新的种子时候多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这是会降低并发性能。ThreadLocalRandom解决这个问题

ThreadLocalRandom基本使用

//获取当前线程的随机数生成器
ThreadLocalRandom rand = ThreadLocalRandom.current();
for(int i = 0; i < 10; i ++){
    System.out.println(rand.nextInt(5) );
}

ThreadLocal的出现就是为了解决多线程访问一个变量时候需要进行同步的问题,让每一个线程拷贝一份变量,每个线程对变量进行操作时候实际是操作自己本地内存里面的拷贝,从而避免了对共享变量进行同步。Random的缺点是多个线程会使用原子性种子变量,会导致对原子变量更新的竞争。ThreadLocalRandom 让每个线程维护自己的一个种子变量,每个线程生成随机数时候根据自己老的种子计算新的种子,并使用新种子更新老的种子,然后根据新种子计算随机数,就不会存在竞争问题,这会大大提高并发性能