ThreadLocalRandom原理剖析

作者时间邮箱
潘顾昌2020/06/20guchang.pan@hand-china.com

1.介绍

ThreadLocalRandom是JDK7在JUC包下新增的随机数生成器,它弥补了Random在多线程下的缺陷。

2.Random类及其局限性

public class Demo03 {
    public static void main(String[] args) {
        Random random = new Random();
        System.out.println(random.nextInt(100));
    }
}

随机数的生成需要一个默认的种子,这个种子是个long类型的数字,你可以在创建Random时通过构造函数指定,如果不指定,默认构造函数内部会生成一个默认的种子。

 /**
     * Creates a new random number generator. This constructor sets
     * the seed of the random number generator to a value very likely
     * to be distinct from any other invocation of this constructor.
     */
    public Random() {
        this(seedUniquifier() ^ System.nanoTime());
    }

有了默认的种子,如何生成随机数呢?

public int nextInt(int bound) {
    // 参数检查
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
    // 根据老种子生成新的种子(1)
        int r = next(31);
    // 指定bound以内的随机值
        int m = bound - 1;
    // 判断bound是否为2的倍数,根据不同情况使用不同的函数处理,使用新的种子生成随机值(2)
        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;
    }

可以看到,步骤(1)是个固定的函数,seed=f(seed),random每次都是用老的种子去生成新的种子,如果两个线程老的种子一致,就是导致生成的新的种子是一样的。

再看步骤(2),如果两个线程生成的新的种子是一致的,必然导致这两个线程生成的随机值是一致的。

综上,我们必须保证多线程生成的种子是不一样的,此时就需要保证int r = next(31)的原子性。也就是说多个线程根据老的种子计算新的种子的时候,第一个线程的新种子计算出来后,其他线程要丢弃自己老的种子,而使用第一个线程计算出来的新种子计算自己的新种子。

知道了原理,那Random函数如何实现的呢?

private final AtomicLong seed;
/**
     * Generates the next pseudorandom number. Subclasses should
     * override this, as this is used by all other methods.
     *
     * <p>The general contract of {@code next} is that it returns an
     * {@code int} value and if the argument {@code bits} is between
     * {@code 1} and {@code 32} (inclusive), then that many low-order
     * bits of the returned value will be (approximately) independently
     * chosen bit values, each of which is (approximately) equally
     * likely to be {@code 0} or {@code 1}. The method {@code next} is
     * implemented by class {@code Random} by atomically updating the seed to
     *  <pre>{@code (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)}</pre>
     * and returning
     *  <pre>{@code (int)(seed >>> (48 - bits))}.</pre>
     *
     * This is a linear congruential pseudorandom number generator, as
     * defined by D. H. Lehmer and described by Donald E. Knuth in
     * <i>The Art of Computer Programming,</i> Volume 3:
     * <i>Seminumerical Algorithms</i>, section 3.2.1.
     *
     * @param  bits random bits
     * @return the next pseudorandom value from this random number
     *         generator's sequence
     * @since  1.1
     */
    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));
    }

Random函数使用原子变量达到了这个效果,在创建Random时初始化的种子就保存在种子原始变量里面。通过CAS操作保证只有一个线程可以更新老的种子为新的,失败的线程会通过循环重新获取更新后的种子作为当前种子计算出新的种子。

总结:每个Random实例里面都有一个原子性的种子变量记录当前种子的值,当要生成新的随机数时需要根据当前种子计算新的种子并更新新的原子变量。在多线程下使用单个Random实例生成随机数时,当多个线程同时计算随机数生成新的种子时,多个线程会竞争同一个原子变量的更新操作,由于该原子变量的更新为CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,降低了并发性能。所以ThreadLocalRandom应运而生。

3.ThreadLocalRandom

public class Demo03 {
    public static void main(String[] args) {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        System.out.println(random.nextInt(32));
    }
}

ThreadLocalRandom通过让每一个线程都维护一个种子变量,使得在每个线程对变量进行操作时,实际是操作自己本地内存里面的副本,从而避免的对共享变量进行同步。可以理解用拿空间换取时间。

Random:

在这里插入图片描述

ThreadLocalRandom:

在这里插入图片描述

通过使用ThreadLocalRandom,每个线程都维护一个种子变量,则每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新的种子更新老的种子,再根据新种子计算随机值,就不会存在竞争问题了,这会大大提高并发性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潘顾昌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值