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

并发编程系列博客传送门


随机数

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

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

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

随机数发生器

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

//发生器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. $CF888G\ Xor-MST$ 最小生成树

    正解:最小生成树 解题报告: 传送门$QwQ$ 发现$Kruskal$和$Prime$都不太可做,于是考虑$B$算法. 先大概港下$B$算法的流程趴$QwQ$.大概就,每次对每个联通块找到最近的联通块 ...

  2. 「洛谷P1233」木棍加工 解题报告

    P1233 木棍加工 题目描述 一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的.棍子可以被一台机器一个接一个地加工.机器处理一根棍子之前需要准备时间.准备时间是这样定义的: 第一根棍子的准备时间 ...

  3. 1087 有多少不同的值 (20 分)C语言

    当自然数 n 依次取 1.2.3.--.N 时,算式 ⌊n/2⌋+⌊n/3⌋+⌊n/5⌋ 有多少个不同的值?(注:⌊x⌋ 为取整函数,表示不超过 x 的最大自然数,即 x 的整数部分.) 输入格式: ...

  4. shell脚本一键配置本地yum源

    效果如图: #!/bin/bash #Author:GaoHongYu #QQ: #Time:-- :: #Name:yumgz.sh #Version:V1. CKGZ=$( df -Th | gr ...

  5. Spring Cloud Stream消息驱动@SendTo和消息降级

    参考程序员DD大佬的文章,自己新建demo学习学习,由于需要消息回执,看到了@SendTo这个注解能够实现,下面开始学习demo,新建两个项目cloud-stream-consumer消费端 和 cl ...

  6. C#事件(Event): 发布符合 .NET Framework Guidelines 的事件

    本文翻译整理自:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-publish-event ...

  7. docker 批量删除 镜像 容器

    我们在docker构建和测试时,经常会产生很多无用的镜像或者容器,我们可用如下两条命令一个一个删除. docker container rm 容器id #删除容器 可简写: docker rm 容器i ...

  8. Office系列(1)---将Office文件(Word、PPT、Excel)转换为PDF文件

    需求: 将Office文件作为文章并在网页上预览,主要为(Word.PPT.Excel)3种类型文件. 研究了一下,找到了两种解决方案 直接调用微软的在线预览功能实现(预览前提:预览资源必须可以直接通 ...

  9. 状态压缩 hdu #10

    You are playing CSGO. There are n Main Weapons and m Secondary Weapons in CSGO. You can only choose ...

  10. Java.前端.Layer.open.btn验证无效

    今天遇到了一个很可笑的问题,在.Layer弹窗open中设置了多个按钮,只有yes按钮有效,btn2点击后直接关闭弹窗,排查了2个小时后终于解决,就是btn2要return false! var in ...