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解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java NIO实现多人聊天室

    Java NIO实现多人聊天室

    这篇文章主要为大家详细介绍了Java NIO实现多人聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Java动态线程池插件dynamic-tp集成过程浅析

    Java动态线程池插件dynamic-tp集成过程浅析

    这篇文章主要介绍了Java动态线程池插件dynamic-tp集成过程,dynamic-tp是一个轻量级的动态线程池插件,它是一个基于配置中心的动态线程池,线程池的参数可以通过配置中心配置进行动态的修改
    2023-03-03
  • springboot接收日期字符串参数与返回日期字符串类型格式化

    springboot接收日期字符串参数与返回日期字符串类型格式化

    这篇文章主要介绍了springboot接收日期字符串参数与返回日期字符串类型格式化,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Java8 ArrayList之forEach的使用

    Java8 ArrayList之forEach的使用

    这篇文章主要介绍了Java8 ArrayList之forEach的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java探索之Feign入门使用详解

    Java探索之Feign入门使用详解

    这篇文章主要介绍了Java探索之Feign入门使用详解,关于feign,我也是第一次遇到,于是在网上搜集了相关文章,这篇比较详细,介绍了其简介,选择feign的原因以及其他相关内容,需要的朋友可以参考下。
    2017-10-10
  • Java绘图库JFreeChart的使用教程

    Java绘图库JFreeChart的使用教程

    图表是一种以简单方式显示信息的图形,JFreeChart允许创建各种交互式和非交互式图表,本文主要介绍了Java绘图库JFreeChart的使用教程,感兴趣的可以了解一下
    2023-09-09
  • Jenkins+Maven+SVN自动化部署java项目

    Jenkins+Maven+SVN自动化部署java项目

    这篇文章主要介绍了Jenkins+Maven+SVN自动化部署java项目,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • springboot集成rabbitMQ之对象传输的方法

    springboot集成rabbitMQ之对象传输的方法

    这篇文章主要介绍了springboot集成rabbitMQ之对象传输的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • java使用文件流实现查看下载次数

    java使用文件流实现查看下载次数

    这篇文章主要为大家详细介绍了java使用文件流实现查看下载次数,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • Gitlab CI-CD自动化部署SpringBoot项目的方法步骤

    Gitlab CI-CD自动化部署SpringBoot项目的方法步骤

    本文主要记录如何通过Gitlab CI/CD自动部署SpringBoot项目jar包。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07

最新评论