2.ThreadLocalRandom

news2024/11/17 15:41:46

Random类及其局限性

一般情况下,我们都会使用java.util.Random来生成随机数(Math.random()也是使用Random实例生成随机数)。

示例

public static void main(String[] args) {

    Random random = new Random();
    for (int i = 0; i < 10; i++) {
        System.out.println(random.nextInt(10));
    }

}

分析

下面以nextInt(int bound) 方法为例来分析Random的源码

public int nextInt(int bound) {
    //边界检测
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    //获取下一随机数
    int r = next(31);
    //(*)此处以特定算法根据r计算出最终结果
    ...
    return r;
}
protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    //CAS操作更新seed
    do {
        oldseed = seed.get();
        //根据老的种子计算新的种子
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

由此可见,生成新的随机数需要两步:

  • 根据老的种子生成新的种子
  • 由新的种子计算出新的随机数

单线程下每次调用nextInt都会根据老的种子计算出新的种子,可以保证随机性。

但多线程下,不同线程可能拿着同一个老的种子去计算新种子,如果next方法因此返回相同的值的话,由于( * )处的算法是固定的,这会导致不同线程生成相同的随机数,这并非我们想要的。所以next方法使用CAS操作保证每次只有一个线程可以更新老的种子,失败的线程则重新获取,这样就解决了上述问题。

但这样处理仍有一个缺陷:当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于该操作为CAS操作,同时只有一个线程会成功,这样会造成大量的自旋重试,导致并发性能降低。而ThreadLocalRandom可以完美解决此问题。

ThreadLocalRandom

每个线程新增种子变量threadLocalRandomSeed,每个线程根据老的种子变量变成新的种子。

image-20230612141448179

ThreadLocalRandom类继承了Random类并重写了nextInt方法,在ThreadLocalRandom没有直接继承Random类的原子性种子变量。

ThreadLocalRandom没有存放具体的种子,具体的种子放在具体调用线程的threadLocalRandomSeed变量里。

ThreadLocalRandom类似在ThreadLocal类,是个工具类。当线程调用ThreadLocalRandom的current方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子。

当调用ThreadLocalRandom的nextInt方法时,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新的种子,然后更新新的种子到当前线程的threadLocalRandomSeed变量,然后再根据新种子并使用具体算法计算随机数。

threadLocalRandomSeed变量就是Thread里的一个普通long变量,不是原子性变量。

seeder和probeGenerator是两个原子性变量,在初始化调用线程种子和探针变量时会用到它们,每个线程只会使用一次。

变量instance是ThreadLocalRandom的一个实例,变量是static的。

当多线程通过ThreadLocalRandom的current方法获取ThreadLocalRandom实例时,其实获取的是一个实例。具体种子存放在线程里,所有ThreadLocalRandom实例里只包含与线程无关的通用算法,所以是线程安全的。

示例

public static void main(String[] args) {

    Random random = ThreadLocalRandom.current();
    for (int i = 0; i < 10; i++) {
        System.out.println(random.nextInt(10));
    }

}

原理

Random的缺点在于多个线程会使用同一个原子性变量,从而导致对原子变量的竞争;而ThreadLocalRandom保证每个线程都维护一个种子变量,每个线程根据自己老的种子生成新的种子,避免了竞争问题,大大提高了并发性能。

源码分析

Unsafe机制

// Unsafe机制
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实例
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        // 获取Thread类里threadLocalRandomSeed变量在Thread的偏移量
        SEED = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSeed"));
        // 获取Thread类里threadLocalRandomProbe变量在Thread的偏移量
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        // 获取Thread类里threadLocalRandomSecondarySeed变量在Thread的偏移量,这个值在LongAdder中会用到
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

ThreadLocalRandom current()方法

// ThreadLocalRandom current()方法
// 获取ThreadLocalRandom实例,并初始化调用线程中的threadLocalRandomSeed和threadLocalRandomProb变量
static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
    //检测是否初始化过
    //PROBE为Thread类中threadLocalRandomProb偏移
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)// 说明当前线程是第一次调用ThreadLocalRandom的current方法
        localInit();// 计算当前线程的初始化种子变量 目的为了延迟初始化,在不需要使用随机数功能时就不初始化Thread类中的种子变量,是一种优化
    return instance;
}

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);// 计算当前线程中threadLocalRandomProb的初始化值
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));// 根据sedder计算当前线程的初始化种子
    Thread t = Thread.currentThread();
    //SEED为Thread类中threadLocalRandomSeed内存偏移
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

如果线程中第一次调用current()方法,则调用localInit()进行初始化设置当前线程中的threadLocalRandomProb和threadLocalRandomSeed变量

这个是静态方法多次调用的是同一个ThreadLocalRandom实例。

下面来看int nextInt(int bound)方法

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    //根据当前Thread中的threadLocalRandomSeed变量生成新种子    
    int r = mix32(nextSeed());
    //根据新种子和bound计算随机数
    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;
}

final long nextSeed() {
    Thread t; long r;
    //生成并存入新种子
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                    r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

如上,首先调用nextSeed()根据当前Thread中的threadLocalRandomSeed变量生成并存入新种子,然后经过特定算法得出了nextInt的值。

首先使用 r = UNSAFE.getLong(t, SEED) 获取当前线程中threadLocalRandomSeed变量的值,然后在种子的基础上累加GAMMA值作为新种子,而后使用UNSAFE.putLong方法把心种子放入当前线程的threadLocalRandomSeed变量中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/707282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

热搜第一!携程宣布每生一个娃奖5万!

6月30日&#xff0c;在线旅游平台携程集团宣布&#xff0c;他们全新推出一项针对全球员工的生育补贴政策。 从2023年7月1日开始&#xff0c;入职满3年的全球员工&#xff0c;无论你是男是女&#xff0c;每生育一个孩子&#xff0c;就将为你获得每年一万元的现金补贴。而这份补贴…

从vue2学习vue3

vue3与vue2的区别 Vue 3在写法上与Vue 2有一些区别&#xff0c;以下是一些常见的区别&#xff1a;引入方式&#xff1a;Vue 2使用Vue全局对象进行引入&#xff0c;而Vue 3使用createApp函数创建应用实例。组件注册&#xff1a;Vue 2中使用Vue.component全局方法或在选项中使用…

Python用if-else实现【猜数字】

文章目录 Python 猜数字案例代码实现&#xff1a;运行效果&#xff1a; Python 猜数字案例 设置一个范围1-100的随机整数变量&#xff0c;通过while循环&#xff0c;配合input语句&#xff0c;判断输入的数字是否等于随机数 无限次机会&#xff0c;直到猜中为止每一次猜不中&am…

1.Springboot基础篇笔记

Springboot基础篇 1.快速上手Springboot 1.基础配置 1.parent 通过配置指定父工程依赖为spring-boot-starter-paerent实现了版本管理的功能&#xff0c;再导入springboot整合其他技术的jar包即可 作用&#xff1a;指定jar包版本信息信息&#xff0c;避免依赖版本冲突 这个s…

理解 flask中request中form、data、json、values属性的区别

flask的request对象中关于请求参数的获取有几个不同的属性,例如 args、form、data、json。估计大部分人一开始也分不清什么情况下哪个属性有值,哪个属性没值,这篇文章全面整理了这几个属性之间的区别和使用场景。 flask.request对象其实是对HTTP请求的一种封装,我们知道HT…

Fidder抓包工具基础+postman调试测试

1、下载地址 汉化Fiddler Web Debugger(5.0.20192.25092)中文版 2、配置参数初始化 开启 fidder 百度即可 3、抓包 核心&#xff1a; 入参 出参 4、如何把包的数据放到postman发生请求 保存一个文件&#xff0c;选择格式 保存文件 自定义名称 将har 包托人 postman 即可 &…

华为OD机试真题B卷 Python 实现【箱子之形摆放】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出3、说明 一、题目描述 要求将一批箱子按从上到下以‘之’字形的顺序摆放在宽度为 n 的空地上&#xff0c;输出箱子的摆放位置&#xff0c;例如&#xff1a;箱子ABCDEFG&am…

Autosar诊断实战系列08-Dem与BswM在ECU运行期间究竟擦出了哪些火花

本文框架 前言1. Dem与BswM交互内容先睹为快2 Dem与BswM交互的配置开发2.1 ESH_DemInit Rule2.2 Dem_Shutdown的处理规则3. Dem在初始化及Shutdown期间的工作内容3.1 Dem初始化期间工作内容3.2 Dem_ShutDown期间工作内容前言 在本系列笔者将结合工作中对诊断实战部分的应用经验…

九齐NY8A051D硬件IR模块的配置和使用以及解码处理

写在前面 最近在群里有看到有小伙伴在问九齐的硬件IR模块&#xff0c;突然想起来我好像用过&#xff0c;找了半天才把项目找出来&#xff0c;写篇博客记录一下方便下次使用和寻找。 按道理来说九齐所有内置硬件IR模块的MCU都是可以用类似的方法配置和使用的&#xff0c;举一反…

packaged_task创建异步任务

本文将介绍基本的packaged_task的用法&#xff1a; 看完上述代码后一定有疑问&#xff1a; 为什么要有packaged_task&#xff1f; 解答&#xff1a;上文的线程通信代码如下&#xff1a; 功能主要是做了求两数之和&#xff0c;然而一般情况下&#xff0c;我们并不习惯这样编写子…

Hive(15):Table(表)DDL操作

1 Describe table Hive中的DESCRIBE table语句用于显示Hive中表的元数据信息 describe formatted [db_name.]table_name; describe extended [db_name.]table_name; 如果指定了EXTENDED关键字,则它将以Thrift序列化形式显示表的所有元数据。如果指定了FORMATTED关键字,则它…

大数据Doris(五十三):MySQL Dump 导出

文章目录 MySQL dump 导出 一、Dump导出案例 二、注意事项 MySQL Dump 导出 mysqldump是一个常用的 MySQL 数据库备份工具&#xff0c;它可以将 MySQL 数据库中的数据导出为 SQL 格式的文件&#xff0c;从而实现对数据的备份、迁移和恢复等操作。Doris 在0.15 之后的版本已…

Flutter 实现任意控件拖动功能

文章目录 前言一、如何实现&#xff1f;1、使用GestureDetector响应拖动事件2、使用Transform变换控件位置3、计算活动区域 二、完整代码三、使用示例1、基本用法 总结 前言 使用flutter开发是需要控件能拖动&#xff0c;比如画板中的元素&#xff0c;或者工具条&#xff0c;搜…

递归入门-数据结构和算法教程

什么是递归&#xff1f; 函数直接或间接调用自身的过程称为递归&#xff0c;相应的函数称为递归函数。使用递归算法&#xff0c;某些问题可以很容易地解决。这样的问题的示例是河内塔&#xff08;TOH&#xff09;、中序/前序/后序树遍历、图的DFS等。递归函数通过调用自身的副…

网络安全(黑客)自学建议

建议一&#xff1a;黑客七个等级 黑客&#xff0c;对很多人来说充满诱惑力。很多人可以发现这门领域如同任何一门领域&#xff0c;越深入越敬畏&#xff0c;知识如海洋&#xff0c;黑客也存在一些等级&#xff0c;参考知道创宇 CEO ic&#xff08;世界顶级黑客团队 0x557 成员…

基于微信小程序的英语单词记忆系统的设计与实现(论文+源码)_kaic

摘 要 当前时期&#xff0c;国内的经济获得了非常快速的发展&#xff0c;互联网技术在持续的创新和完善&#xff0c;教育教学方面也在不断的进步&#xff0c;教育全面深化改革在发展&#xff0c;并且移动互联网技术在教育领域获得了大量的实践以及应用。语言的全球化慢慢的变…

【算法】01背包和完全背包

文章目录 背包问题概览01背包二维dp数组写法一维dp数组写法 完全背包关于遍历顺序相关题目[416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)[279. 完全平方数](https://leetcode.cn/problems/perfect-squares/)[518. 零钱兑换 II](https://leet…

【webrtc】vs2017 重新构建m98

配置了一台13900k的主机,需要重新配置webrtc 构建环境代码已经gclient sync 同步好了,打算重新构建:vs2017 的win10 sdk最大17763 vs2017 环境 set vs2017_install=S:\Program Files (x86)\Microsoft Visual Studio\2017\Communitywin10 SD

Python_上下文管理器

目录 上下文管理器类 多上下文管理器 contextmanager实现上下文管理器 上下文管理器(context manager)是 Python 编程中的重要概念&#xff0c;用于规定某个对象的使用范围。一旦进入或者离开该使用范围&#xff0c;会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语…

【Kubernetes资源篇】ConfigMap配置管理中心详解

文章目录 一、ConfigMap配置中心理论知识1、ConfigMap配置中心简介2、ConfigMap局限性 二、创建ConfigMap的四种方式1、第一种&#xff1a;通过命令行创建ConfigMap2、第二种&#xff1a;通过指定文件创建ConfigMap3、第三种&#xff1a;通过指定目录创建ConfigMap4、第四种&am…