当前位置:   article > 正文

并发高的情况下,试试用ThreadLocalRandom来生成随机数_threadlocalrandom.current()

threadlocalrandom.current()

一:简述

如果我们想要生成一个随机数,通常会使用Random类。但是在并发情况下Random生成随机数的性能并不是很理想,今天给大家介绍一下JUC包中的用于生成随机数的类--ThreadLocalRandom.(本文基于JDK1.8)

二:Random的性能差在哪里

Random随机数生成是和种子seed有关,而为了保证线程安全性,Random通过CAS机制来保证线程安全性。从next()方法中我们可以发现seed是通过自旋锁和CAS来进行修改值的。如果在高并发的场景下,那么可能会导致CAS不断失败,从而导致不断自旋,这样就可能会导致服务器CPU过高。

  1. protectedintnext(int bits) {
  2. long oldseed, nextseed;
  3. AtomicLongseed=this.seed;
  4. do {
  5. oldseed = seed.get();
  6. nextseed = (oldseed * multiplier + addend) & mask;
  7. } while (!seed.compareAndSet(oldseed, nextseed));
  8. return (int)(nextseed >>> (48 - bits));
  9. }
  10. 复制代码

三:ThreadLocalRandom的简单使用

使用的方法很简单,通过ThreadLocalRandom.current()获取到ThreadLocalRandom实例,然后通过nextInt(),nextLong()等方法获取一个随机数。

代码:

  1. @Testvoidtest()throws InterruptedException {
  2. newThread(()->{
  3. ThreadLocalRandomrandom= ThreadLocalRandom.current();
  4. System.out.println(random.nextInt(100));
  5. }).start();
  6. newThread(()->{
  7. ThreadLocalRandomrandom= ThreadLocalRandom.current();
  8. System.out.println(random.nextInt(100));
  9. }).start();
  10. Thread.sleep(100);
  11. }
  12. 复制代码

运行结果:

四:为什么ThreadLocalRandom能在保证线程安全的情况下还能有不错的性能

我们可以看一下ThreadLocalRandom的代码实现。

首先我们很容易看出这是一个饿汉式的单例

  1. /** Constructor used only for static singleton */privateThreadLocalRandom() {
  2. initialized = true; // false during super() call
  3. }
  4. /** The common ThreadLocalRandom */staticfinalThreadLocalRandominstance=newThreadLocalRandom();
  5. 复制代码

我们可以看到PROBE成员变量代表的是Thread类的threadLocalRandomProbe属性的内存偏移量,SEED成员变量代表的是Thread类的threadLocalRandomSeed属性的内存偏移量,SECONDARY成员变量代表的是Thread类的threadLocalRandomSecondarySeed属性的内存偏移量。

  1. // Unsafe mechanicsprivatestaticfinal sun.misc.Unsafe UNSAFE;
  2. privatestaticfinallong SEED;
  3. privatestaticfinallong PROBE;
  4. privatestaticfinallong SECONDARY;
  5. static {
  6. try {
  7. UNSAFE = sun.misc.Unsafe.getUnsafe();
  8. Class<?> tk = Thread.class;
  9. SEED = UNSAFE.objectFieldOffset
  10. (tk.getDeclaredField("threadLocalRandomSeed"));
  11. PROBE = UNSAFE.objectFieldOffset
  12. (tk.getDeclaredField("threadLocalRandomProbe"));
  13. SECONDARY = UNSAFE.objectFieldOffset
  14. (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
  15. } catch (Exception e) {
  16. thrownewError(e);
  17. }
  18. }
  19. 复制代码

可以看到Thread类中确实有这三个属性

Thread类:

  1. @sun.misc.Contended("tlr")
  2. //当前Thread的随机种子 默认值是0long threadLocalRandomSeed;
  3. /** Probe hash value; nonzero if threadLocalRandomSeed initialized */@sun.misc.Contended("tlr")
  4. //用来标志当前Thread的threadLocalRandomSeed是否进行了初始化 0代表没有,非0代表已经初始化 默认值是0int threadLocalRandomProbe;
  5. /** Secondary seed isolated from public ThreadLocalRandom sequence */@sun.misc.Contended("tlr")
  6. //当前Thread的二级随机种子 默认值是0int threadLocalRandomSecondarySeed;
  7. 复制代码

接下来我们看ThreadLocalRandom.current()方法。

ThreadLocalRandom.current()

ThreadLocalRandom.current()的作用主要是初始化随机种子,并且返回ThreadLocalRandom的实例。

首先通过UNSAFE类获取当前线程的Thread对象的threadLocalRandomProbe属性,看随机种子是否已经初始化。没有初始化,那么调用localInit()方法进行初始化

  1. publicstatic ThreadLocalRandom current() {
  2. // 获取当前线程的if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
  3. localInit();
  4. return instance;
  5. }
  6. 复制代码

localInit()

localInit()方法的作用就是初始化随机种子,可以看到代码很简单,就是通过UNSAFE类对当前Thread的threadLocalRandomProbe属性和threadLocalRandomSeed属性进行一个赋值。

  1. staticfinalvoidlocalInit() {
  2. intp= probeGenerator.addAndGet(PROBE_INCREMENT);
  3. intprobe= (p == 0) ? 1 : p; // skip 0longseed= mix64(seeder.getAndAdd(SEEDER_INCREMENT));
  4. Threadt= Thread.currentThread();
  5. UNSAFE.putLong(t, SEED, seed);
  6. UNSAFE.putInt(t, PROBE, probe);
  7. }
  8. 复制代码

接下来以nextInt()方法为例,看ThreadLocalRandom是如何生成到随机数的。我们可以看出随机数正是通过nextSeed()方法获取到随机种子,然后通过随机种子而生成。所以重点看nextSeed()方法是如何获取到随机种子的。

  1. publicintnextInt(int bound) {
  2. if (bound <= 0)
  3. thrownewIllegalArgumentException(BadBound);
  4. intr= mix32(nextSeed());
  5. intm= bound - 1;
  6. if ((bound & m) == 0) // power of two
  7. r &= m;
  8. else { // reject over-represented candidatesfor (intu= r >>> 1;
  9. u + m - (r = u % bound) < 0;
  10. u = mix32(nextSeed()) >>> 1)
  11. ;
  12. }
  13. return r;
  14. }
  15. 复制代码

nextSeed()

nextSeed()方法的作用是获取随机种子,代码很简单,就是通过UNSAFE类获取当前线程的threadLocalRandomSeed属性,并且将原来的threadLocalRandomSeed加上GAMMA设置成新的threadLocalRandomSeed。

  1. finallongnextSeed() {
  2. Thread t; long r; // read and update per-thread seed
  3. UNSAFE.putLong(t = Thread.currentThread(), SEED,
  4. r = UNSAFE.getLong(t, SEED) + GAMMA);
  5. return r;
  6. }
  7. 复制代码

小结:

ThreadLocalRandom为什么线程安全?是因为它将随机种子保存在当前Thread对象的threadLocalRandomSeed变量中,这样每个线程都有自己的随机种子,实现了线程级别的隔离,所以ThreadLocalRandom也并不需要像Random通过自旋锁和cas来保证随机种子的线程安全性。在高并发的场景下,效率也会相对较高。

注:各位有没有发现ThreadLocalRandom保证线程安全的方式和ThreadLocal有点像呢

需要注意的点:

1.ThreadLocalRandom是单例的。

2.我们每个线程在获取随机数之前都需要调用一下ThreadLocalRandom.current()来初始化当前线程的随机种子。

3.理解ThreadLocalRandom需要对UnSafe类有所了解,它是Java提供的一个可以直接通过内存对变量进行获取和修改的一个工具类。java的CAS也是通过这个工具类来实现的。

最后

如果有什么疑问,欢迎在下方留言或者。最后,原创不易,如果本文对你有所帮助,那么点个赞再走吧。

作者:沉迷学习的罗师傅

原文链接:https://juejin.cn/post/7096289192140865544

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/84011
推荐阅读
相关标签
  

闽ICP备14008679号