zoukankan      html  css  js  c++  java
  • JUC-ThreadLocalRandom

    这个类是在JDK7中新增的随机数生成器,它弥补了Random类在多线程下的缺陷。

    Radndom类的局限性

    在JDK7之前包括现在java.util.Random都是使用比较广泛的随机数生成工具。为什么说它在多线程中有缺陷,看下面一个例子:

    public class RandomTest {
        public static void main(String[] args) {
            Random random=new Random();
            for (int i = 0; i <10 ; i++) {
                System.out.println(random.nextInt(5));
            }
        }
    }
    

    这是生成随机数常用的一种方法。随机数的生成需要一个默认的种子,这个种子其实是一个long类型的数字,你可以在创建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;
        }
    

    由此可见生成随机数需要两步:

    • 首先根据老的种子来生成新的种子
    • 然后根据新的种子来计算新的随机数
      但是在多线程环境中,有可能多个线程同时拿到同一个老种子来计算新种子,这样多线程会产生相同值得随机数。
      要解决这个问题,首先我们得保证原子性,也就是说当多个线程去拿老种子的时候,第一个线程的新种子被计算出来后,第二个线程要丢弃自己的老种子,使用第一个线程的新种子来计算自己的新种子。
      Random函数使用了一个原子变量达到了这个效果。
    
     private final AtomicLong seed;
    
    protected int next(int bits) {
            long oldseed, nextseed;
            AtomicLong seed = this.seed;
            do {
                oldseed = seed.get();
                nextseed = (oldseed * multiplier + addend) & mask;
            } while (!seed.compareAndSet(oldseed, nextseed));
            return (int)(nextseed >>> (48 - bits));
        }
    

    while (!seed.compareAndSet(oldseed, nextseed))这一步使用CAS操作,它使用新种子来更新老种子,但是可能有多个线程同时拿到了老种子,然后根据nextseed = (oldseed * multiplier + addend) & mask;算出来的新种子也是一样的。但是在while操作中由于是CAS操作那么只会有一个线程更新种子成功,失败的线程会通过循环重新去获取更新过后的种子,这样通过原子变量和CAS操作就解决了上诉问题。保证了随机数的随机性。但是我们都知道CAS操作在多线程中必然会造成自旋重试,这将会降低并发性能,所以ThreadLocalRandom应运而生。

    ThreadLocalRandom

    public class ThreadLocalRandomTest {
        static void random(){
            ThreadLocalRandom random=ThreadLocalRandom.current();
            for (int i = 0; i <10 ; i++) {
                System.out.println(random.nextInt(5));
            }
            System.out.println("--------------");
        }
        public static void main(String[] args) throws  Exception{
           Thread thread1=new Thread(() -> {
               random();
           });
            Thread thread2=new Thread(() -> {
                random();
            });
            Thread thread3=new Thread(() -> {
                random();
            });
            Thread thread4=new Thread(() -> {
                random();
            });
            thread1.start();
            Thread.sleep(3000);
            thread2.start();
            Thread.sleep(3000);
            thread3.start();
            Thread.sleep(3000);
            thread4.start();
        }
    }
    

    其实从名字上我们可以联想到ThreadLocal这个类,实际上这个类也是这个原理,Random的缺点是多个线程会使用同一个原子变量,从而导致对原子变量的更新竞争,导致大量的自旋重试。
    那么我们可以让每一个线程维护一个种子变量,每个线程生成随机数的时候根据自己老的种子来计算新的种子。就不会存在竞争问题了,这会大大提高并发性能。

  • 相关阅读:
    跨库查询数据
    使用StringBuilder与SqlParameter
    sql 循环 随机数创建数据
    sql like N'%...%' 在C#里的写法
    Html 空格与换行
    Python之随机数
    C#之使用随机数
    C#之ArrayList
    Unity之读取本地图片
    GAN(Generative Adversarial Nets)的发展
  • 原文地址:https://www.cnblogs.com/xhj928675426/p/14462909.html
Copyright © 2011-2022 走看看