导航菜单
首页 » 淡然如水111 » 正文

乐器-Random在高并发下的缺点以及JUC对其的优化

作者:CoderBear
原文:https://juejin.im/post/5cbc1e3bf265da039d32819c

Random能乐器-Random在高并发下的缺点以及JUC对其的优化够说是每个开发都知道,而且都用的很6的类,假如你说,你没有用过Random,也不知道Random是什么鬼,那么你也不会来到这个技能类型的社区,也看不到我的博客了。但并不是每个人都知道Random的原理,知道Random在高并发下的缺点的人应该更少。这篇博客,我就来剖析下Random类在并发下的缺点以及JUC对其的优化。

Random的原理及缺点

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

在学习编程的时分,我一直对JDK开发人员很不解:为什么发作随机数的办法名是:“”nextXXX”?雨过天晴我英语只逗留“允许yes,摇头no,来是come,去是go” 的水平,可是我知道next是“下一个”的意思,假如我来命名,会命名为“create”,“generate”,这样不是更“恰当”吗?为什么JDK开发人员会命名为“nextXXX”呢?莫非是他们忽然“词穷”了,想不出什么单词了,所以爽性随意来一个?后来才知道,本来经过Random生成的随机数,并不是真实的随机,它有一个种子的概念,是依据种子值来核算【下一个】值的,假如种子值相同,那么它生成出来的随机数也必定持平,也便是“确乐器-Random在高并发下的缺点以及JUC对其的优化认的输入发作确认的输出”。

假如你不信的话,咱们能够来做一个试验:

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

Random类除了供给无参的结构办法以外,还供给了有参的结构办法,咱们能够传入int类型的参数,这个参数乐器-Random在高并发下的缺点以及JUC对其的优化就被称为“种子”,这样“种子”就固定了,生成的随机数也都是相同了:

41
41
41
41
41
41
41
41
41
41

让咱们简略的看下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办法去除做了什么:

  1. 界说了旧种子oldseed,下一个种子(新种子)nextseed。
  2. 取得旧的种子的值,赋值给oldseed 。
  3. 一个奥秘的算法,核算出下一个种子(新种子)赋值给nextseed。
  4. 运用CAS操作,假如seed的值仍是oldseed,就用nextseed替换掉,而且回来true,!true为false,退出while循环;假如seed的值现已不为oldseed了,就阐明seed的值现已被替换过了,回来false,!false为true,持续下一次while循环。
  5. 一个奥秘的算法,依据nextseed核算出随机数,而且回来。

咱们能够看到中心就在第四步,我再来更详细的的描绘下,首要要知道seed的类型:

 private final AtomicLong seed;

seed的类型是AtomicLong,是一个原子操作类,能够确保原子性,seed.get便是取得seed详细的值,se乐器-Random在高并发下的缺点以及JUC对其的优化ed便是咱们所谓的种子,也便是种子值保存在了原子变量去除。当有两个线程一起进入到next办法,会发作如下的工作:

  1. 线程A,线程B一起拿到了seed的值,赋值给oldseed变量。
  2. 依据一个奥秘的算法,核算出nextseed为XXX。留意,已然这个算法是固定的,那么生成出来的nextseed也必定是固定的。
  3. 进入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

首要,让咱们来看看ThreadLo南安普顿大学calRandom的运用办法:

能够看到运用办法发作了少许的改动,咱们来看看ThreadLocalRandom中心代码的完成逻辑:

current

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

有一点需求留意,因为current是一个静态的办法,所以屡次调用此办法,回来的ThreadLocalRandom对象是同一个。

假如当时线程的PROBE是0,阐明是第一次调用current办法,那么需求调用localInit办法,不然直接回来现已发作的实例。

localInit

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

nextInt

和Random类下的nextXXX办法的原理相同,也是依据旧的种子生成新的种子,然后依据新的种子来生成随乐器-Random在高并发下的缺点以及JUC对其的优化机数,咱们来看下nextSeed办法做了什么:

nextSeed

首要运用UNSAFE.getLong(t, SEED) 来取得当时线程的SEED,随后+上GAMMA来作为新的种子值,随后将新的种子值放到当时线程中。

总结

本文首要简略的剖析了Random的完成原理,引出nextXXX办法在高并发下的缺点:需求竞赛种子乐器-Random在高并发下的缺点以及JUC对其的优化原子变量。接着介绍了ThreadLocalRandom的运用办法以及原理,从类的命名,就能够看出完成原理类似于ThreadLocal,seed种子是保存在每个线程中的,也是依据每个线程中的seed来核算新的种子的,这样就避免了竞赛的问题。

二维码