当前位置:   article > 正文

Random 高并发下的缺点(美团面试题) 替代方案JUC下的 ThreadLocalRandom_c# 高并发环境下使用 random

c# 高并发环境下使用 random

Random 高并发下的缺点

  • Random是线程安全的,但是并不是“高并发”的
  • 大量CAS导致CPU飙高
 Random random = new Random();
 System.out.println(random.nextInt(100));
  • 1
  • 2

一、奇怪的命名

  • random.nextInt() 奇怪的命名,获取值为什么不是通常的 “get”, “create”,“generate”等常见的命名.random生成的随机数,并不是真正的随机,它有一个种子的概念,是根据种子值来计算【下一个】值的,如果种子值相同,那么它生成出来的随机数也必定相等,也就是“确定的输入产生确定的输出”。
    在这里插入图片描述
    二、源码解析
public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);

	// 1.根据老的种子生成新的种子
    int r = next(31);
    // 2.根据新的种子计算随机数
    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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

剖析 next(int bits)方法

  1. 获取当前seed种子值 oldseed
  2. 根据当前古老神秘算法计算下个种子值 nextseed
  3. seed.compareAndSet(oldseed, nextseed): 比较当前seed和 oldseed 值是否相同(乐观锁,比较当前seed是否被修改).两者一致把nextseed赋值给当前seed
  4. 最后一个神秘的算法,根据nextseed计算出随机数seed,并且返回

三、并发下
在这里插入图片描述

  1. T1、T2同时拿到了seed值
  2. 计算nextseed的公式是固定的,所以计算后的nextseed必然相同
  3. 假设T1优先继续执行seed和oldseed 值相同,把nextseed赋值给当前seed
  4. T2继续执行利用CAS算法,发现seed的值已经不为oldseed了,因为线程T1已经把seed的值替换成了nextseed了,所以CAS失败,只能再次循环。再次循环的时候, seed.get()就拿到了最新的种子值,再次根据一个神秘的算法获得了nextSeed,CAS成功,退出循环

看起来一切很美好,其实不然,.使用CAS操作更新seed,在大量线程竞争的场景下,这个CAS操作很可能失败,失败了就会重试,而这个重试又会消耗CPU运算,从而使得性能大大下降了。

解决方案 JUC包下的ThreadLocalRandom
ThreadLocalRandom current = ThreadLocalRandom.current();
int num = current.nextInt(10);
  • 1
  • 2

current()

// 1.静态方法每次返回的对象都是同一个
// 2.如果当前线程的PROBE是0,说明是第一次调用current方法,那么需要调用localInit方法,否则直接返回已经产生的实例。
public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

current.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)
            ;
    }
    return r;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

nextSeed()

和Random类下的nextXXX方法的原理一样,也是根据旧的种子生成新的种子,然后根据新的种子来生成随机数

final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/84132
推荐阅读
相关标签
  

闽ICP备14008679号