java并发高的情况下用ThreadLocalRandom来生成随机数

目录
  • 一:简述
  • 二:Random的性能差在哪里
  • 三:ThreadLocalRandom的简单使用
  • 四:为什么ThreadLocalRandom能在保证线程安全的情况下还能有不错的性能

一:简述

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

二:Random的性能差在哪里

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

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

三:ThreadLocalRandom的简单使用

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

代码:

    @Test
    void test() throws InterruptedException {
        new Thread(()->{
            ThreadLocalRandom random = ThreadLocalRandom.current();
            System.out.println(random.nextInt(100));
        }).start();
        new Thread(()->{
            ThreadLocalRandom random = ThreadLocalRandom.current();
            System.out.println(random.nextInt(100));
        }).start();

        Thread.sleep(100);
    }

运行结果:

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

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

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

    /** Constructor used only for static singleton */
    private ThreadLocalRandom() {
        initialized = true; // false during super() call
    }

    /** The common ThreadLocalRandom */
    static final ThreadLocalRandom instance = new ThreadLocalRandom();

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

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        SEED = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSeed"));
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

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

Thread类:

    @sun.misc.Contended("tlr")
    //当前Thread的随机种子 默认值是0
    long threadLocalRandomSeed;

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    //用来标志当前Thread的threadLocalRandomSeed是否进行了初始化 0代表没有,非0代表已经初始化 默认值是0
    int threadLocalRandomProbe;

    /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    //当前Thread的二级随机种子 默认值是0
    int threadLocalRandomSecondarySeed;

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

ThreadLocalRandom.current()

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

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

public static ThreadLocalRandom current() {
        // 获取当前线程的
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
    }

localInit()

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

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

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

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

nextSeed()

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

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

小结:

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

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

需要注意的点:

1.ThreadLocalRandom是单例的。

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

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

到此这篇关于java并发高的情况下用ThreadLocalRandom来生成随机数的文章就介绍到这了,更多相关java ThreadLocalRandom生成随机数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java并发高的情况下用ThreadLocalRandom来生成随机数

    目录 一:简述 二:Random的性能差在哪里 三:ThreadLocalRandom的简单使用 四:为什么ThreadLocalRandom能在保证线程安全的情况下还能有不错的性能 一:简述 如果我们想要生成一个随机数,通常会使用Random类.但是在并发情况下Random生成随机数的性能并不是很理想,今天给大家介绍一下JUC包中的用于生成随机数的类--ThreadLocalRandom.(本文基于JDK1.8) 二:Random的性能差在哪里 Random随机数生成是和种子seed有关,而为

  • Java线程在什么情况下可以终止

    目录 线程结束的三个原因 具体分析 1.使用标志位推出线程 2.使用stop方法强制终止线程 3.使用interrupt终止线程 哪些情况可以终止线程的进行 题目解析 答案选C A:线程使用sleep()方法,使线程挂起一段时间,并不是终止 B: 创建一个新线程时,对之前的线程没有影响 C:抛出一个例外,线程终止 D: 并不是终止,而是抢占,进程是资源分配的最基本单位,同一个进程创建的不同线程共享这些资源,当某一个线程优先级比较高时,它就会抢占其他线程的资源,导致其他线程没有资源可用,会造成阻塞

  • Java并发工具类LongAdder原理实例解析

    LongAdder实现原理图 高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性.既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源. LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同

  • Java并发底层实现原理学习心得

    我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为汇编语言,然后转为操作系统指令,然后转为1,0,最后CPU进行识别执行. 提到java的并发,我们不由的就会想到java中常见的键字:volatile和synchronized,我们接下来就会从这两个关机字展开分析: volatile的底层实现原理 synchronized的实现原理和应用 volatile 说到volatile,在java

  • Java中生成随机数的实现方法总结

    在实际开发工作中经常需要用到随机数.如有些系统中创建用户后会给用户一个随机的初始化密码.这个密码由于是随机的,为此往往只有用户自己知道.他们获取了这个随机密码之后,需要马上去系统中更改.这就是利用随机数的原理.总之随机数在日常开发工作中经常用到.而不同的开发语言产生随机数的方法以及技巧各不相同.笔者这里就以Java语言为例,谈谈随机数生成的方法以及一些技巧. 一.利用random方法来生成随机数. 在Java语言中生成随机数相对来说比较简单,因为有一个现成的方法可以使用.在Math类中,Java

  • Java中生成随机数的4种方式与区别详解

    目录 在 Java 中,生成随机数的场景有很多,所以本文我们就来盘点一下 4 种生成随机数的方式,以及它们之间的区别和每种生成方式所对应的场景. 1.Random Random 类诞生于 JDK 1.0,它产生的随机数是伪随机数,也就是有规则的随机数.Random 使用的随机算法为 linear congruential pseudorandom number generator (LGC) 线性同余法伪随机数.在随机数生成时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的

  • java高并发情况下高效的随机数生成器

    前言 在代码中生成随机数,是一个非常常用的功能,并且JDK已经提供了一个现成的Random类来实现它,并且Random类是线程安全的. 下面是Random.next()生成一个随机整数的实现: protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend)

  • J2ee 高并发情况下监听器实例详解

    J2ee 高并发情况下监听器实例详解 引言:在高并发下限制最大并发次数,在web.xml中用过滤器设置参数(最大并发数),并设置其他相关参数.详细见代码. 第一步:配置web.xml配置,不懂的地方解释一下:参数50通过参数名maxConcurrent用在filter的实现类中获取,filter-class就是写的实现类, url-pattern就是限制并发时间的url,结束! <filter> <filter-name>ConcurrentCountFilter</filt

  • 数据库高并发情况下重复值写入的避免 字段组合约束

    10线程同时操作,频繁出现插入同样数据的问题.虽然在插入数据的时候使用了: insert inti tablename(fields....) select @t1,@t2,@t3 from tablename where not exists (select id from tablename where t1=@t1,t2=@t2,t3=@t3) 当时还是在高并发的情况下无效.此语句也包含在存储过程中.(之前也尝试线判断有无记录再看是否写入,无效). 因此,对于此类情况还是需要从数据库的根本

  • java web在高并发和分布式下实现订单号生成唯一的解决方案

    方案一: 如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不同订单的生成时间戳正常不同,因此用时间戳+随机数(或自增数)就可以区分各个订单.如果存在并发,且订单号是由一个进程中的多个线程产生的,那么只要把线程ID添加到序列号中就可以保证订单号唯一.如果存在并发,且订单号是由同一台主机中的多个进程产生的,那么只要把进程ID添加到序列号中就可以保证订单号唯一.如果存在并发,且订单号是由不同台主机产生的,那么MAC地址.IP地址或CPU序列号等能够区分主机的号码添加到序列号中就可以保

随机推荐