《Java并发编程之美》读书笔记——ThreadLocalRandom类原理剖析

news2024/11/27 22:31:11

文章目录

  • 1.Random类的局限性
  • 2.ThreadLocalRandom
  • 3.源码分析
    • Unsafe机制
    • current()方法
    • int nextInt(int bound)方法

1.Random类的局限性

在JDK之前包括现在,java.util.Random都是使用比较广泛的随机数生成工具类。

下面我们先来看一下Random的使用方法。

// 创建一个默认种子的随机数生成器
Random random = new Random();
// 输出10个在0~5(包含0不包含5)之间的随机数
for(int i = 0; i < 10; i++)
	System.out.println(random.nextInt());

首先我们要知道随机数的生成需要一个种子,这个种子其实是一个Long类型的数字。

你可以在使用Random的构造函数的时候指定种子,也可以不指定使用默认的种子。

接下来我们再来看看有了种子以后如何生成随机数。

public int nextInt(int bound) { 
	// 参数检查
	if (bound <= 0)
		throw new IllegalArgumentException(BadBound);
	// 根据老的种子生成新的种子 
    int r = next(31); 
	// 根据新的种子计算随机数 
	...
	return r; 
}

这段代码简而言之就是两个步骤:

  1. 首先根据旧的种子生成新的种子
  2. 根据新的种子生成随机数

如果是在单线程下,这样是没有毛病的。但是如果在多线程的情况下,问题就来了。

多线程情况下,多个线程拿着相同的种子去计算随机数,因为计算随机数的算法是固定的,所以这些拿到相同种子的线程会生成相同的随机数。这不是我们想看到的。所以生成种子的这一步要保证原子性!简单来说就是第一个线程生成新的种子以后,第二个线程就要丢弃旧的种子,根据第一个线程所生成的新种子计算自己的新种子。

所以在上面一段代码中的next()方法中,官方解决办法是,使用原子类AtomicLong中的CAS操作。

下面我们直接看代码

protected int next(int bits) { 
	long oldseed, nextseed; 
	AtomicLong seed = this.seed;
	do {
		// 获取当前原子变量的种子值
		oldseed = seed.get(); 
		// 根据旧的种子值计算新的种子值
		nextseed = (oldseed * multiplier + addend) & mask;
		// 使用CAS操作,保证更新种子的操作具有原子性。这样就只有一个线程可以更新种子值,其他线程只有等待上一线程更新完毕才能更新新的种子。
	} while (!seed.compareAndSet(oldseed, nextseed)); 
	//(9)
	return (int)(nextseed >>> (48 - bits));
}

至此,问题就暴露出来了,更新种子这个过程,在多线程情况下,大量线程自旋重试,会降低并发性能!所以才有了本文的重点ThreadLocalRandom

2.ThreadLocalRandom

看一下如何始使用:

public class RandomTest {
	public static void main(String[] args) {
        // 获取一个随机数生成器
		ThreadLocalRandom random = ThreadLocalRandom.current();
		// 输出10个在0~5(包含0,不包含5)之间的随机数 
        for (int i = 0; i < 10; ++i) {
		System.out.println(random.nextInt(5));
		} 
    }
}

3.源码分析

首先看ThreadLocalRandom的类图结构

请添加图片描述

ThreadLocalRandom 中并没有存放具体的种子,具体的种子存放在具体的调用线程的 threadLocalRandomSeed 变量里面。ThreadLocalRandom 类似于 ThreadLocal 类,就是个工具类。当线程调用 ThreadLocalRandomcurrent 方法时,ThreadLocalRandom 负责初始化 调用线程的 threadLocalRandomSeed 变量,也就是初始化种子。

当调用ThreadLocalRandom类中的nextInt方法时,实际上就是获取当前线程中的threadLocalRandomSeed变量来计算新的种子,然后将新的种子更新到当前线程的threadLocalRandomSeed变量中。这里要注意threadLocalRandomSeed是一个普通的Long类型的变量,因为这个变量是线程级别的,所以根本不需要使用原子性变量。

说到这里如果你不理解,可以看看我上一篇博客中关于ThreadLocal的原理。

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

current()方法

该方法用于获取ThreadLocalRandom实例,并初始化调用线程中threadLocalRandomSeedthreadLocalRandomProbe

static final ThreadLocalRandom instance = new ThreadLocalRandom(); 

public static ThreadLocalRandom current() {
    // 该方法首先会判断线程中的threadLocalRandomProbe是否为零(线程中这个值默认为零)
	if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        // 如果为零,则说明线程是第一次调用该方法,那么就需要初始化当前线程的种子变量
		localInit();
	return instance; 
}

static final void localInit() {
    // 首先使用probeGenerator中的方法计算当前线程中threadLocalRandomProbe的值
	int p = probeGenerator.addAndGet(PROBE_INCREMENT); 
    // skip 0
    int probe = (p == 0) ? 1 : p;
    // 计算当前线程的初始化种子
	long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT)); 
    // 获得当前执行线程
    Thread t = Thread.currentThread();
    // 把上面两个变量设置到当前线程
	UNSAFE.putLong(t, SEED, seed);
	UNSAFE.putInt(t, PROBE, probe);
}

这里为了延迟初始化,在不需要使用随机数功能时就不初始化 Thread 类中的种子变量,这是一种优化。

同时我们需要注意,这是一个静态方法,多个线程返回的是同一个ThreadLocalRandom变量。

int nextInt(int bound)方法

public int nextInt(int bound) { 
    //参数校验
	if (bound <= 0)
		throw new IllegalArgumentException(BadBound);
	//根据当前线程中的种子计算新种子 
    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; 
}

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

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

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

相关文章

kubelet源码 删除pod(一)

k8s版本为1.25.3版本 kubectl delete pod name当删除一个pod的时候会经历一下流程 kubectl会发pod消息给api server。apiserver将信息存入etcd&#xff0c;然后返回确认信息。apiserver开始反馈etcd中pod对象的变化&#xff0c;其他组件使用watch机制跟踪apiserver上的变动。…

行业生态重塑中,新氧如何逆风翻盘

美东时间11月18日盘前&#xff0c;中国互联网医美第一股新氧科技发布2022财年第三季度的业绩报告&#xff0c;业绩符合其业绩指引。 据新氧该季度财报显示&#xff0c;第三季度实现非美国通用会计准则归属于新氧科技的净利润990万元人民币&#xff08;140万美元&#xff09;。…

表格分组标签:表格行分组中的隐藏功能

在程序员的认知中&#xff0c;表格中存在行分组标签&#xff0c;也就是thead&#xff0c;tbody&#xff0c;tfoot三个行分组标签。也许你会认为我在这里还是为大家继续讲解thead&#xff0c;tbody&#xff0c;tfoot三个标签&#xff0c;那就大错特错了 今天除了要讲解他的基础作…

栈和队列

声明&#xff1a;本文主要作为作者的复习笔记&#xff0c;由于作者水平有限&#xff0c;难免有错误和不准确之处&#xff0c;欢迎读者批评指正。 目录快捷跳转线性表接口两个常用子类什么时候选择ArrayList&#xff0c;什么时候选择LinkedList?栈和队列的关系栈栈的实现根据使…

ASP.NET Core教程-Exception(异常和错误处理)

更新记录 转载请注明出处&#xff1a; 2022年11月22日 发布。 2022年11月20日 从笔记迁移到博客。 错误处理基础 错误处理说明 ASP.NET Core中的错误处理分为&#xff1a; ​ 局部Controller中处理错误 ​ 在Controller中定义错误代码和转到错误界面即可 ​ 全局应用中设置错误…

vue.js毕业设计,基于vue.js前后端分离教室预约系统设计与实现(H5移动项目)

功能模块 【后台管理功能模块】 系统设置&#xff1a;设置关于我们、联系我们、加入我们、法律声明 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信息&#xff0c…

[附源码]计算机毕业设计JAVA家政管理系统

[附源码]计算机毕业设计JAVA家政管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

Spring Boot中Node.js的下载与Vue CLI在IDEA中的部署及使用(图文解释 简单易懂)

仍有问题可点赞关注收藏后在评论区留言~~~ 一、Node.js与npm的下载 在使用Vue CLI(Vue脚手架)搭建前端系统的时候&#xff0c;因为需要用到npm安装Vue CLI&#xff0c;而npm是集成在Node.js中的&#xff0c;所以需要首先安装Node.js Node.js官网 下载过程很简单&#xff0c;…

数据库错误知识集2

Oracle数据库中最常见的索引类型是b-tree索引&#xff0c;也就是B-树索引&#xff0c;以其同名的计算科学结构命名。 union与union all的区别&#xff08;摘&#xff09;&#xff1a; ①对重复结果的处理&#xff1a;union会去掉重复记录&#xff0c;union all不会&#xff1b;…

转铁蛋白偶联糖(单糖/多糖),(Transferrin)TF-PEG-Dextran葡聚糖/Lysozyme溶菌酶

产品名称&#xff1a;转铁蛋白-聚乙二醇-葡聚糖 英文名称&#xff1a;TF-PEG-Dextran 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观:固体或粘性液体&#xff0c;取决于分子量 PEG分子量可选&#xff1a;350、550、750、1k、2k、34k、5k…

校招补一个什么样的项目比较好?

校招一年比一年卷&#xff0c;千军万马过独木桥的情况下该怎样充实自己的项目经历&#xff1f;有两件事要搞明白&#xff01; 一、什么是【好】项目&#xff1f; 好项目在简历上要能一眼看出亮点和提问点。并且要能够把提问点对应的回答准备到位&#xff0c;这样才能在招聘量…

外汇天眼周回顾:Equiti开设最新办事处,Vantage推出Vantage Connect服务

在过去的一周内&#xff0c;国外外汇市场都发生了哪些引人注意的外汇新闻&#xff1f;天眼君带大家一起看看&#xff0c;具体新闻如下&#xff1a; 1、Equiti在塞浦路斯利马索尔开设最新办事处 据悉&#xff0c;多资产经纪商Equiti Group在宣布其在塞浦路斯的新业务获得CySEC …

CRM客户管理系统在市面上这么多?应该如何选型?各行业选型CRM必看!

当您下定决心怎样为您的民营企业选择合适的 CRM 时&#xff0c;须要考量很多不利因素。许多基本上国际标准适用于绝大多数寻求 CRM 软件系统的民营企业。其他注意事宜取决于您的业务体量和性质。下列是任何人 CRM 软件系统中须要注意的 14 项常规事宜&#xff0c;以及许多可能对…

【笑小枫玩转SpringBoot系列】目录,一篇拥有一个系列,值得收藏哟~

本系列简介 本系列主要讲解了JAVA后端开发中常用的操作&#xff0c;以初创一个SpringBoot项目开始&#xff0c;以实例的形式讲解了一个单项目框架的诞生。本文可以做为SpringBoot项目的入门学习&#xff0c;也可以当做一个初建项目的框架。 本文主要使用mysql数据库&#xff0…

Flutter for App——一个简单的BMI计算APP

一个简单的BMI计算APP效果截图初始化布局顶部区域标题计算结果组合顶部区域背景中间区域输入框输入行计算按钮分界线组合中间区域底部区域页面组合BMI计算Toast弹窗效果导入依赖封装效果截图 初始化 初始化表单控制器和焦点节点 void initView(){formKey GlobalKey<FormS…

Unity ECS实例:制作俯视角射击游戏!

目录 创建主角 3&#xff1a;主角移动和摄像机跟随 4&#xff1a;实现敌人角色 5&#xff1a;子弹&#xff0c;死亡&#xff0c;机器人 6&#xff1a;粒子与音效 这次我们来使用Unity ECS系统制作一个俯视角度的射击游戏。虽然现在网上有不少ECS的资料和项目&#xff0c;但…

(十二)Spring IoC注解式开发

文章目录回顾注解注解怎么定义&#xff0c;注解中的属性怎么定义&#xff1f;元注解Target注解Retention注解注解怎么使用&#xff1f;通过反射机制怎么读取注解&#xff1f;Spring注解原理声明Bean的注解Spring注解的使用第一步&#xff1a;加入aop的依赖第二步&#xff1a;在…

1.3 c++虚基类的用途以及内存模型

1.3 虚基类 1.3.1 虚基类(菱形继承)的语法实现 对于如下的继承体系&#xff0c;定义了一个公共基类A。类B和类C都由类A公有派生&#xff0c;类D由类B和类C公有派生。 其示例代码如下所示&#xff0c;这段代码的45行是无法通过编译器的&#xff0c;这即是多重继承存在的一个问…

十二、组合API(2)

本章概要 响应式 API reactive() 方法watchEffect() 方法解构响应性状态深入 watchEffect()refreadonlycomputedwatch 11.3 响应式 API Vue 3.0 的核心功能主要是通过响应式 API 实现的&#xff0c;组合 API 将他们公开为独立的函数。 11.3.1 reactive() 方法 reactive()…

基于物联网的自动灌溉系统的设计与实现

本设计是基于物联网的自动灌溉系统&#xff0c;主要实现以下功能&#xff1a; 1&#xff0c;OLED显示温湿度和土壤温湿度&#xff1b; 2&#xff0c;可通过继电器实现自动灌溉和自动加热的功能&#xff1b; 3&#xff0c;通过lora构建自组网&#xff0c;进行主从机间的数据传输…