Random高并发下的缺陷,JUC对其的优化_juc 并发编程 千锋教育

本文分析了Java中的nextInt方法在高并发场景下的问题,重点介绍了如何通过使用ThreadLocalRandom和原子操作(如CAS)来确保随机数生成的线程安全,避免种子冲突导致的随机数重复。
摘要由CSDN通过智能技术生成

让我们简单的看下nextInt的源码把,源码涉及到算法,当然算法不是本篇博客讨论的重点,我们可以把源码简化成如下的样子:



public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    //1.根据老的种子生成新的种子
    int r = next(31);
    //2.根据新的种子计算随机数
    …
    return r;
}


**nextXXX的核心代码可以被简化这两步。**


* 首先是根据老的种子生成新的种子
* 然后是根据新的种子计算出随机数


现在让我们想一个问题,如果在高并发的情况下,有N个线程。


`同时执行`到`第一步`:根据老的种子生成新的种子,获得的种子不就一样了吗?


由于第二步是根据新的种子来计算随机数,这个算法又是固定的,会产生什么情况?N个线程最终获得的随机数不都一样了吗?


显然这不是我们想要的,所以JDK开发人员想到了这点,让我们看看next方法里面做了什么:



protected int next(int bits) {
    //定义旧种子,下一个种子
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        //获得旧的种子值,赋值给oldseed
        oldseed = seed.get(); 
        //一个神秘的算法
        nextseed = (oldseed * multiplier + addend) & mask;
    //CAS,如果seed的值还是为oldseed,就用nextseed替换掉,并且返回true,退出while循环,如果已经不为oldseed了,就返回false,继续循环
    } while (!seed.compareAndSet(oldseed, nextseed));
    //一个神秘的算法
    return (int)(nextseed >>> (48 - bits));
}


* 定义了旧种子oldseed,下一个种子(新种子)nextseed。
* 获得旧的种子的值,赋值给oldseed 。
* 一个神秘的算法,计算出下一个种子(新种子)赋值给nextseed。
* 使用CAS操作,如果seed的值还是oldseed,就用nextseed替换掉,并且返回true,!true为false,退出while循环;如果seed的值已经不为oldseed了,就说明seed的值已经被替换过了,返回false,!false为true,继续下一次while循环。
* 一个神秘的算法,根据nextseed计算出随机数,并且返回。


我们可以看到核心就在第四步,我再来更详细的的描述下,首先要知道seed的类型:



private final AtomicLong seed;


seed的类型是AtomicLong,是一个原子操作类,可以保证原子性,seed.get就是获得seed具体的值。


seed就是我们所谓的种子,也就是种子值保存在了原子变量里面。当有两个线程同时进入到next方法,会发生如下的事情:


* 线程A,线程B同时拿到了seed的值,赋值给oldseed变量。
* 根据一个神秘的算法,计算出nextseed为XXX。注意,既然这个算法是固定的,那么生成出来的nextseed也必定是固定的。
* 进入while循环:3.1  线程A,利用CAS算法,发现seed的值还是为oldseed,说明seed的值没有被替换过,就把seed的值替换成第二步生成出来的nextseed,替换成功,返回true,!true为false,退出while循环。3.2 线程B,利用CAS算法,发现seed的值已经不为oldseed了,因为线程A已经把seed的值替换成了nextseed了啊,所以CAS失败,只能再次循环。再次循环的时候, seed.get()就拿到了最新的种子值,再次根据一个神秘的算法获得了nextSeed,CAS成功,退出循环。


看起来一切很美好,其实不然,如果并发很高,会发生什么?大量的线程都在进行while循环,这是相当占用CPU的,所以JUC推出了`ThreadLocalRandom`来解决这个问题。


### ThreadLocalRandom


首先,让我们来看看ThreadLocalRandom的使用方法:



public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
        System.out.println(threadLocalRandom.nextInt(100));
    }
}


可以看到使用方式发生了些许的改变,我们来看看ThreadLocalRandom核心代码的实现逻辑:


#### current



public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}


有一点需要注意,由于current是一个静态的方法,所以多次调用此方法,返回的ThreadLocalRandom对象是同一个。


如果当前线程的PROBE是0,说明是第一次调用current方法,那么需要调用localInit方法,否则直接返回已经产生的实例。


#### localInit



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


首先初始化probe和seed,随后调用UNSAFE类的方法,把probe和seed设置到当前的线程,这样其他线程就拿不到了。


#### nextInt



public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    int r = mix32(nextSeed());
    int m = bound - 1;
    if ((bound & m) == 0) // power of two
        r &= m;
    else { // reject over-represented candidates
        for (int u = r >>> 1;
             u + m - (r = u % bound) < 0;
             u = mix32(nextSeed()) >>> 1)
            ;
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以点击这里获取
入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!**

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以点击这里获取

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值