本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门


随机数

随机数在科学研究与工程实际中有着极其重要的应用!

简单来说,随机数就是一个数列,这个数列可能满足一定的概率分布,又获取其满足的分布并不为我们所知。

数学方法产生随机数应该称之为“伪随机数”,只有使用物理方法才能得到真正的随机数!因此我们使用计算机产生的随机数都是"伪随机数"。那么计算机到底是怎么产生随机数的呢?这时就要提到随机数发生器了。

随机数发生器

我们高中的时候都学过数列的知识,上面提到随机数可以看成是一个数列,那么我们可以将随机数发生器看成是一个数列表达式。比如现在有下面两个随机说发生器

//发生器1
X(n+1)= a * X(n) + b //发生器2
X(n+1)= a * X(n)

当然还有很多随机数发生器,现实生产中使用的发生器也并不是像上面的那么简单,这边只是为了说明随机数发生器到底是什么列了两个例子。

随机数种子

我们在产生随机数的时候经常会听到随机数种子这个名词,那随机数种子到底是什么?我们还是以上面的发生器为例。

//发生器1
X(n+1)= a * X(n) + b

显然通过上式我们能够得到一个数列,前提是X(0)应该给出,依次我们就可以算出X(1),X(2)...;当然不同的X(0)就会得到不同的数列。

可以说X(0)的值就是随机数的种子,只要这个种子给的一样,产生的随机数序列就是一样的。下面给出一个使用Java中￿￿Random产生随机数的列子证明下这个说法。

Random random1 = new Random(100);
for (int i = 0; i < 10 ; i++) {
System.out.println(random1.nextInt(5));
} System.out.println("-------------"); Random random2 = new Random(100);
for (int i = 0; i < 10 ; i++) {
System.out.println(random2.nextInt(5));
}

执行结果如下:

0
0
4
3
1
1
1
3
3
3
-------------
0
0
4
3
1
1
1
3
3
3
--------------

上面代码中新建了两个随机数发生器,都设置了同样的随机数种子100,产生10个随机数。从上面的结果中可以看出两个发生器产生的序列是一样的。

对于一个应用级的伪随机数发生器,所有的“伪随机数”,均匀的分布于一个“轨道”上,几乎所有的数都可以做为种子。数字“0”,有时是一个特例,不能作为种子,当然它取决于你使用的随机数发生器!

Random类的局限性

Random类是JDK提供的一个随机数发生器。 我们看下Random类中nextInt方法的源代码:


public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//关键代码点,这边会根据老的随机数种子生成新的随机数种子,然后会根据新生成的随机数种子生成随机数
int r = next(31);
int m = bound - 1;
if ((bound & m) == 0) // i.e., bound is a power of 2
r = (int)((bound * (long)r) >> 31);
else {
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;
}

那我们看下上面的next方法到底是怎样生成新的随机数种子的。


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

上面代码中,首先获取当前原子变量种子的值,然后根据当前种子值计算新的种子。再然后使用CAS机制更新种子的值,保证多线程竞争的情况下只有一个能更新成功。最后使用固定算法根据新的种子计算随机数。

每个Random实例里面都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数时需要根据当前种子计算新的种子并更新回原子变量。在多线程下使用单个Random实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能。

分析到这里我们可以看出Random的局限性并不是线程安全的问题,而是在大量线程并发的时候,通过CAS机制更新随机数种子会导致大量线程自旋,耗费CPU性能,导致系统吞吐量下降。

ThreadLocalRandom

ThreadLocalRandom类是JDK 7在JUC包下新增的随机数生成器,它弥补了Random类在多线程下的缺陷。下面来分析下ThreadLocalRandom的实现原理。

从名字上看它会让我们联想到ThreadLocal。ThreadLocal通过让每一个线程复制一份变量,使得在每个线程对变量进行操作时实际是操作自己本地内存里面的副本,从而避免了对共享变量进行同步。

实际上ThreadLocalRandom的实现也是这个原理,Random的缺点是多个线程会使用同一个原子性种子变量,从而导致对原子变量更新的竞争。

那么,如果每个线程都维护一个种子变量,则每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新种子更新老的种子,再根据新种子计算随机数,就不会存在竞争问题了,这会大大提高并发性能。

ThreadLocalRandom提升性能的原理就是这样的。具体的源代码也比较简单,这边就不贴代码了。感兴趣的可以自己看下。下面贴下ThreadLocalRandom的简单使用方法

ThreadLocalRandom random = ThreadLocalRandom.current();
random.nextInt();

参考

  • 《Java并发编程之美》

ThreadLocalRandom ---- 提升Random在大并发下的效率的更多相关文章

  1. Random在高并发下的缺陷以及JUC对其的优化

    Random可以说是每个开发都知道,而且都用的很6的类,如果你说,你没有用过Random,也不知道Random是什么鬼,那么你也不会来到这个技术类型的社区,也看不到我的博客了.但并不是每个人都知道Ra ...

  2. .net的页面在大并发下出现503错误

    .net的页面在大并发下偶尔出现503错误 我们开发了一个回调页面,由一个工具负责调用,由于压力非常大,回调页面通过6台服务器负载均衡的: 最近业务系统又再次扩容,回调页面压力成倍增加,在高峰时间段偶 ...

  3. Atitit 提升开发进度大方法--高频功能与步骤的优化 类似性能优化

    Atitit 提升开发进度大方法--高频功能与步骤的优化 类似性能优化 1. 通用功能又可以组合成crud模块1 1.1. 查询(包括步骤,发送查询dsl,通讯返回结果,绑定到表格控件)2 1.2. ...

  4. Atitit 提升进度的大原则与方法  高层方法  attilax总结

    Atitit 提升进度的大原则与方法  高层方法  attilax总结 生产力的提升点 1.1. 管理,管理的发展发展非常缓慢,1 1.2. 方法论(前后分离,dsl等)1 1.3. 工具( 工具链 ...

  5. PHP使用redis防止大并发下二次写入(如如何防止重复下订单)

    php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写. $lock_k ...

  6. 大并发下TCP内存消耗优化小记(86万并发业务正常服务)

    转自:http://blog.csdn.net/u010954257/article/details/54178160 最近在做一个大并发服务的测试(目前测到86万,当然有大量长连接,每天打的日志高到 ...

  7. PHP使用redis防止大并发下二次写入

    php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写. $lock_k ...

  8. 利用job提升马哈鱼数据血缘分析效率

    利用job提升马哈鱼数据血缘分析效率 一.Job基本知识 前面文章中已介绍马哈鱼的基本功能,其中一个是job,job其实是一个任务集合处理的概念,就是让用户通过job,可以一次递交所有需要处理的 SQ ...

  9. 为什么要使用ThreadLocalRandom代替Random生成随机数

    799 java里有伪随机型和安全型两种随机数生成器,伪随机生成器根据特定公式将seed转换成新的伪随机数据的一部分,安全随机生成器在底层依赖到操作系统提供的随机事件来生成数据. 安全随机生成器 需要 ...

随机推荐

  1. 「学习笔记」ST表

    问题引入 先让我们看一个简单的问题,有N个元素,Q次操作,每次操作需要求出一段区间内的最大/小值. 这就是著名的RMQ问题. RMQ问题的解法有很多,如线段树.单调队列(某些情况下).ST表等.这里主 ...

  2. 「Luogu P2201」数列编辑器 解题报告

    数列编辑器,在线IDE 本期的主题是洛谷的在线IDE 小学生?!小学生虐我

  3. 前端加密MD5

    今天接触了MD5加密方式,记录一下使用方法,又去搜了搜关于MD5的详细内容 MD5在vue中使用方法 1.下载MD5模块 cnpm install md5 -S 2.引入模块 const md5 = ...

  4. Ant Design框架中不同的组件访问不同的models中的数据

    Ant Design框架中不同的组件访问不同的models中的数据 本文记录了我在使用该框架的时候踩过的坑,方便以后查阅. 一.models绑定 在某个组件(控件或是页面),要想从某个models中获 ...

  5. ASP.NET Core 启用跨域请求

    本文翻译整理自:https://docs.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.1 一 .Cross-Orig ...

  6. ACM北大暑期课培训第三天

    今天讲的内容是深搜和广搜 深搜(DFS) 从起点出发,走过的点要做标记,发现有没走过的点,就随意挑一个往前走,走不 了就回退,此种路径搜索策略就称为“深度优先搜索”,简称“深搜”. bool Dfs( ...

  7. beetlex网关之聚合和url请求过虑

    在这里主要介绍beetlex应用网关的两个插件,分别是聚合和url请求过虑.通过聚合插件可以把整合多个请求的数据来应答请求端,而Url请求过虑同可以拒绝一些有非常关键字的请求. 请求聚合 在网关服务中 ...

  8. swiper如何禁止左右箭头切换

    swiper做项目时,需求 带着左右两边的箭头, 场景1:swiper自动切换,此时左右箭头点击时不能切换   场景2:swiper手动切换,左右箭头可以实现切换,通过翻阅api 终于找到 <s ...

  9. 常用Java静态代码分析工具的分析与比较

    给国产静态代码检测工具Pinpoint打Call! 简介 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了4种现有的主流Java静态代码分析工具 (Checkstyle,FindBugs ...

  10. 使用gravatar生成头像

    avatar代表您在线的图像,当你与网站互动时,你的名字旁边会出现一张图片. Gravatar是一个全球通用的头像.你只需上传一次并创建自己的个人资料,然后当你加入任何支持Gravatar的网站时,你 ...