• 为什么要使用ThreadLocalRandom代替Random生成随机数


    java里有伪随机型和安全型两种随机数生成器,伪随机生成器根据特定公式将seed转换成新的伪随机数据的一部分,安全随机生成器在底层依赖到操作系统提供的随机事件来生成数据。

    安全随机生成器

    • 需要生成加密性强的随机数据的时候才用它
    • 生成速度慢
    • 如果需要生成大量的随机数据,可能会产生阻塞需要等待外部中断事件

    而伪随机生成器,只依赖于“seed”的初始值,如果给生成算法提供相同的seed,可以得到一样的伪随机序列。一般情况下,由于它是计算密集型的(不依赖于任何IO设备),因此生成速度更快。以下是伪随机生成器的进化史。

    java.util.Random 
    自1.0就已经存在,是一个线程安全类,理论上可以通过它同时在多个线程中获得互不相同的随机数,这样的线程安全是通过AtomicLong实现的。 
    Random使用AtomicLong CAS(compare and set)操作来更新它的seed,尽管在很多非阻塞式算法中使用了非阻塞式原语,CAS在资源高度竞争时的表现依然糟糕,后面的测试结果中可以看到它的糟糕表现。

    java.util.concurrent.ThreadLocalRandom 
    1.7增加该类,企图将它和Random结合以克服所有的性能问题,该类继承自Random。

    ThreadLocalRandom的主要实现细节:

    • 使用一个普通的long而不是使用Random中的AtomicLong作为seed
    • 不能自己创建ThreadLocalRandom实例,因为它的构造函数是私有的,可以使用它的静态工厂ThreadLocalRandom.current()
    • 它是CPU缓存感知式的,使用8个long虚拟域来填充64位L1高速缓存行

    测试

    下面进行5种测试:

    1. 一个单独的Random被N个线程共享
    2. ThreadLocal<Random>
    3. ThreadLocalRandom
    4. Random[], 其中每个线程N使用一个数组下标为N的Random
    5. Random[], 其中每个线程N使用一个数组下标为N * 2的Random

    所有的测试都使用封装在RandomTask类里的方法,每个方案都说明了如何使用随机生成器。

    import java.util.Random;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class Test_Random {
    
        private static final long COUNT = 10000000;
        private static final int THREADS = 2;
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("Shared Random");
            testRandom(THREADS, COUNT);
            /*System.out.println("ThreadLocal<Random>");
            testThreadLocal_Random(THREADS, COUNT);
            System.out.println("ThreadLocalRandom");
            testThreadLocalRandom(THREADS, COUNT);
            System.out.println("Shared Random[] with no padding");
            testRandomArray(THREADS, COUNT, 1);
            System.out.println("Shared Random[] with padding");
            testRandomArray(THREADS, COUNT, 2);*/
        }
    
        private static class RandomTask implements Runnable {
            private final Random rnd;
            protected final int id;
            private final long cnt;
            private final CountDownLatch latch;
    
            private RandomTask(Random rnd, int id, long cnt,
                    CountDownLatch latch) {
                super();
                this.rnd = rnd;
                this.id = id;
                this.cnt = cnt;
                this.latch = latch;
            }
    
            protected Random getRandom() {
                return rnd;
            }
    
            @Override
            public void run() {
                try {
                    final Random r = getRandom();
                    latch.countDown();
                    latch.await();
                    final long start = System.currentTimeMillis();
                    int sum = 0;
                    for (long j = 0; j < cnt; j++) {
                        sum += r.nextInt();
                    }
                    final long time = System.currentTimeMillis() - start;
                    System.out.println("Thread #" + id + " Time = " + time / 1000.0 + " sec, sum = " + sum);
                } catch (InterruptedException e) {}
            }
        }
    
        private static void testRandom(final int threads, final long cnt) {
            final CountDownLatch latch = new CountDownLatch(threads);
            final Random r = new Random(100);
            for (int i = 0; i < threads; ++i) {
                final Thread thread = new Thread(new RandomTask(r, i, cnt, latch));
                thread.start();
            }
        }
    
        private static void testRandomArray(final int threads, final long cnt, final int padding) {
            final CountDownLatch latch = new CountDownLatch(threads);
            final Random[] rnd = new Random[threads * padding];
            for (int i = 0; i < threads * padding; ++i) {
                rnd[i] = new Random(100);
            }
            for (int i = 0; i < threads; ++i) {
                final Thread thread = new Thread(new RandomTask(rnd[i * padding], i, cnt, latch));
                thread.start();
            }
        }
    
        private static void testThreadLocalRandom(final int threads, final long cnt) {
            final CountDownLatch latch = new CountDownLatch(threads);
            for (int i = 0; i < threads; ++i) {
                final Thread thread = new Thread(new RandomTask(null, i, cnt, latch) {
                    @Override
                    protected Random getRandom() {
                        // TODO Auto-generated method stub
                        return ThreadLocalRandom.current();
                    }
                });
                thread.start();
            }
        }
    
        private static void testThreadLocal_Random(final int threads, final long cnt) {
            final CountDownLatch latch = new CountDownLatch(threads);
            final ThreadLocal<Random> rnd = new ThreadLocal<Random>() {
    
                @Override
                protected Random initialValue() {
                    // TODO Auto-generated method stub
                    return new Random(100);
                }
    
            };
            for (int i = 0; i < threads; ++i) {
                final Thread thread = new Thread(new RandomTask(null, i, cnt, latch) {
    
                    @Override
                    protected Random getRandom() {
                        // TODO Auto-generated method stub
                        return rnd.get();
                    }
    
                });
                thread.start();
            }
        }
    }

    总结:

    • 任何情况下都不要在多个线程间共享一个Random实例,而该把它放入ThreadLocal之中
    • java7在所有情形下都更推荐使用ThreadLocalRandom,它向下兼容已有的代码且运营成本更低
  • 相关阅读:
    shell 基础进阶 *金字塔
    shell,awk两种方法写9*9乘法表
    shell脚本判断一个用户是否登录成功
    shell 冒泡算法 解决数组排序问题
    shell 石头剪刀布
    应用shell (ssh)远程链接主机
    nmcli命令使用
    光盘yum源autofs按需挂载
    LVM扩容,删除
    LVM创建
  • 原文地址:https://www.cnblogs.com/shamo89/p/8052161.html
Copyright © 2020-2023  润新知