Java中的Random和ThreadLocalRandom详细解析

 更新时间:2024年01月27日 10:29:12   作者:魅Lemon  
这篇文章主要介绍了Java中的Random和ThreadLocalRandom详细解析,Random 类用于生成伪随机数的流, 该类使用48位种子,其使用线性同余公式进行修改,需要的朋友可以参考下

一、Random类

1、简介

Random 类用于生成伪随机数的流。 该类使用48位种子,其使用线性同余公式进行修改

  • Math.random()使用起来相对更简单,但不是线程安全的;
  • java.util.Random的Random类是线程安全的。 但是跨线程的同时使用java.util.Random实例可能会遇到争用,从而导致性能下降。 在多线程设计中考虑使用ThreadLocalRandom类;
  • java.util.Random的Random不是加密安全的。 考虑使用SecureRandom获取一个加密安全的伪随机数生成器,供安全敏感应用程序使用

2、Random的构造函数

Random():创建一个新的随机数生成器

/**
 * Creates a new random number generator. This constructor sets
 * the seed of the random number generator to a value very likely
 * to be distinct from any other invocation of this constructor.
 */
public Random() {
    //System.nanoTime()返回正在运行的Java虚拟机的高分辨率时间源的当前值,以纳秒为单位。
    //这里会调用有参构造函数
    this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
    // 线性同余生成元表  
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L; 
        // 两个很大的数相乘
        if (seedUniquifier.compareAndSet(current, next))
            // 这个比较并且交换CAS
            // 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!
            //如果不是就一直循环!就是为了保证即使在多线程的环境中返回的也是不同的数
            return next;
    }
}
// atomic 这个是 juc 里面修饰的原子性的 long ,get方法说就是获得这个构造函数里面的值
 private static final AtomicLong seedUniquifier
        = new AtomicLong(8682522807148012L);

random(long seed):使用单个 Long 种子创建一个新的随机数生成器

伪随机使用了线性同余法(具体可自行查阅资料)

public Random(long seed) {
    if (getClass() == Random.class)
        this.seed = new AtomicLong(initialScramble(seed));
    else {
        // 子类可能重写了这个不考虑
        this.seed = new AtomicLong(); // 创建一个新的AtomicLong,初始值为 0 
        setSeed(seed);
    }
}
    //清除nextGaussian()使用的haveNextNextGaussian 标志,
    synchronized public void setSeed(long seed) {
        this.seed.set(initialScramble(seed));
        haveNextNextGaussian = false; 
     }
    private static long initialScramble(long seed) {
        return (seed ^ multiplier) & mask;
     }
  private static final long multiplier = 0x5DEECE66DL;
  // x & [(1L << 48)–1]与 x(mod 2^48)等价 取低位48位
  // 带符号左移
  private static final long mask = (1L << 48) - 1;

3、next()核心方法

Random在多线程的环境是并发安全的,它解决竞争的方式是使用用原子类,本质上上也就是CAS + Volatile保证线程安全

在这里插入图片描述

在Random类中,有一个AtomicLong的域,用来保存随机种子。其中每次生成随机数时都会根据随机种子做移位操作以得到随机数。

//Long类型的随机
//long类型在Java中总弄64bit,对next方法的返回值左移32作为long的高位,然后将next方法返回值作为低32位,作为long类型的随机数。
//此处关键之处在于next方法
public long nextLong() {
    return ((long)(next(32)) << 32) + next(32);
}

以下是next方法的核心,使用seed种子,不断生成新的种子,然后使用CAS将其更新,再返回种子的移位后值。这里不断的循环CAS操作种子,直到成功。可见,Random实现原理主要是利用随机种子采用一定算法进行处理生成随机数,在随机种子的安全保证利用原子类AtomicLong。

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

4、Random在并发下的缺点

虽然Random是线程安全,但是对于并发处理使用原子类AtomicLong在大量竞争时,使用同一个 Random 对象可能会导致线程阻塞,由于很多CAS操作会造成失败,不断的Spin,而造成CPU开销比较大而且吞吐量也会下降。

这里可以自行多线程测试,可以发现随着线程增加,Random随着竞争越来越激烈,然后耗时越来越多。然而ThreadLocalRandom随着线程数的增加,基本没有变化。所以在大并发的情况下,随机的选择,可以考虑ThreadLocalRandom提升性能。

二、ThreadLocalRandom

1、简介

ThreadLocalRandom是Random的子类,它是将Seed随机种子隔离到当前线程的随机数生成器,从而解决了Random在Seed上竞争的问题,它的处理思想和ThreadLocal本质相同。

Unsafe 类内的方法透露着一股 “Unsafe” 的气息,具体表现就是可以直接操作内存,而不做任何安全校验,如果有问题,则会在运行时抛出 Fatal Error,导致整个虚拟机的退出。

2、原理分析

2.1 ThreadLocalRandom单例模式

从下述代码可以发现ThreadLocalRandom使用了单例模式,即在一个Java应用中只有一个ThreadLocalRandom对象。

当UNSAFE.getInt(Thread.currentThread(), PROBE)返回0时,就执行localInit(),最后返回单例。

// 一个java应用只有一个实例
// ThreadLocalRandom对象通过ThreadLocalRandom.current()获取,之后可以直接返回随机数
static final ThreadLocalRandom instance = new ThreadLocalRandom();
//获取ThreadLocalRandom对象实例
public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}

2.2 Seed随机种子隔离到当前线程

核心方法

//会从 object 对象var1的内存地址偏移var2后的位置读取四个字节作为long型返回
public native long getLong(Object var1, long var2);
//可以将object对象var1的内存地址偏移var2后的位置后四个字节设置为 var4
public native void putLong(Object var1, long var2, long var4);

UNSAFE.getInt(Thread.currentThread(), PROBE)是获取当前Thread线程对象中的PROBE。

首先获取变量名SEED、PROBE等参数相对对象的偏移位置

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

当第一次调用ThreadLocalRandom.current()方法时当前线程检测到PROBE未初始化会调用localInit()方法进行初始化,并把当前线程的seed值和probe值存储在当前线程Thread对象内存地址偏移相对应变量的位置

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

threadLocalRandomProbe用于表示当前线程Thread是否初始化,如果是非0,表示其已经初始化。

换句话说,该变量就是状态变量,用于标识当前线程Thread是否被初始化。

threadLocalRandomSeed从注释中也可以看出,它是当前线程的随机种子。

随机种子分散在各个Thread对象中,从而避免了并发时的竞争点。

3、nextSeed()核心方法

nextSeed()生成随机种子用来生成随机数序列

public long nextLong() {
    return mix64(nextSeed());
}

因为在初始化的时候已经存储了当前线程的seed值和probe值到相应线程对象内存地址的偏移位置,调用nextSeed()时直接从当前线程对象偏移位置处进行获取,并生成下一个随机数种子到该位置,同时使用了UNSAFE类方法,不同线程间不需要竞争获得seed值,因此可以可以将竞争点隔离

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

三、总结

Random是Java中提供的随机数生成器工具类,但是在大并发的情况下由于其随机种子的竞争会导致吞吐量下降,从而引入ThreadLocalRandom它将竞争点隔离到每个线程中,从而消除了大并发情况下竞争问题,提升了性能。

并发竞争的整体优化思路:lock -> cas + volatile -> free lock

到此这篇关于Java中的Random和ThreadLocalRandom详细解析的文章就介绍到这了,更多相关Random和ThreadLocalRandom解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Resttemplate中设置超时时长方式

    Resttemplate中设置超时时长方式

    这篇文章主要介绍了Resttemplate中设置超时时长方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • java 判断两个时间段是否重叠的案例

    java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • JPA之QueryDSL-JPA使用指南

    JPA之QueryDSL-JPA使用指南

    Springdata-JPA是对JPA使用的封装,Querydsl-JPA也是基于各种ORM之上的一个通用查询框架,使用它的API类库可以写出Java代码的sql,下面就来介绍一下JPA之QueryDSL-JPA使用指南
    2023-11-11
  • SpringBoot整合RabbitMQ实现六种工作模式的示例

    SpringBoot整合RabbitMQ实现六种工作模式的示例

    这篇文章主要介绍了SpringBoot整合RabbitMQ实现六种工作模式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • SpringBoot2零基础到精通之配置文件与web开发

    SpringBoot2零基础到精通之配置文件与web开发

    SpringBoot是一种整合Spring技术栈的方式(或者说是框架),同时也是简化Spring的一种快速开发的脚手架,本篇让我们一起学习配置文件以及web相关的开发
    2022-03-03
  • eclipse创建springboot项目的三种方式总结

    eclipse创建springboot项目的三种方式总结

    这篇文章主要介绍了eclipse创建springboot项目的三种方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 分享Java多线程实现的四种方式

    分享Java多线程实现的四种方式

    这篇文章主要介绍了分享Java多线程实现的四种方式,文章基于 Java的相关资料展开多线程的详细介绍,具有一的的参考价值,需要的小伙伴可以参考一下
    2022-05-05
  • SpringBoot根据注解动态执行类中的方法实现

    SpringBoot根据注解动态执行类中的方法实现

    本文主要介绍了SpringBoot根据注解动态执行类中的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 浅谈java继承中是否创建父类对象

    浅谈java继承中是否创建父类对象

    下面小编就为大家带来一篇浅谈java继承中是否创建父类对象。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • java获取中文拼音首字母工具类定义与用法实例

    java获取中文拼音首字母工具类定义与用法实例

    这篇文章主要介绍了java获取中文拼音首字母工具类定义与用法,结合实例形式分析了java获取中文拼音首字母工具类的具体定义、使用方法及相关操作注意事项,需要的朋友可以参考下
    2019-10-10

最新评论