我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何用Random生成随机数

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何用Random生成随机数

本篇内容介绍了“如何用Random生成随机数”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

前言

在代码中生成随机数,是一个非常常用的功能,并且JDK已经提供了一个现成的Random类来实现它,并且Random类是线程安全的。

下面是Random.next()生成一个随机整数的实现:

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));     return (int)(nextseed >>> (48 - bits)); }

不难看到,上面的方法中使用CAS操作更新seed,在大量线程竞争的场景下,这个CAS操作很可能失败,失败了就会重试,而这个重试又会消耗CPU运算,从而使得性能大大下降了。

因此,虽然Random是线程安全的,但是并不是“高并发”的。

为了改进这个问题,增强随机数生成器在高并发环境中的性能,于是乎,就有了ThreadLocalRandom——一个性能强悍的高并发随机数生成器。

ThreadLocalRandom继承自Random,根据里氏代换原则,这说明ThreadLocalRandom提供了和Random相同的随机数生成功能,只是实现算法略有不同。

在Thread中的变量

为了应对线程竞争,Java中有一个ThreadLocal类,为每一个线程分配了一个独立的,互不相干的存储空间。

ThreadLocal的实现依赖于Thread对象中的ThreadLocal.ThreadLocalMap threadLocals成员字段。

与之类似,为了让随机数生成器只访问本地线程数据,从而避免竞争,在Thread中,又增加了3个成员:

  @sun.misc.Contended("tlr")  long threadLocalRandomSeed;    @sun.misc.Contended("tlr")  int threadLocalRandomProbe;    @sun.misc.Contended("tlr")  int threadLocalRandomSecondarySeed;

这3个字段作为Thread类的成员,便自然和每一个Thread对象牢牢得捆绑在一起,因此成为了名副其实的ThreadLocal变量,而依赖这几个变量实现的随机数生成器,也就成为了ThreadLocalRandom。

消除伪共享

不知道大家有没有注意到,  在这些变量上面,都带有一个注解@sun.misc.Contended,这个注解是干什么用的呢?要了解这个,大家得先知道一下并发编程中的一个重要问题——伪共享

我们知道,CPU是不直接访问内存的,数据都是从高速缓存中加载到寄存器的,高速缓存又有L1,L2,L3等层级。在这里,我们先简化这些负责的层级关系,假设只有一级缓存和一个主内存。

CPU读取和更新缓存的时候,是以行为单位进行的,也叫一个cache line,一行一般64字节,也就是8个long的长度。

因此,问题就来了,一个缓存行可以放多个变量,如果多个线程同时访问的不同的变量,而这些不同的变量又恰好位于同一个缓存行,那会发生什么呢?

 如何用Random生成随机数

如上图所示,X,Y为相邻2个变量,位于同一个缓存行,两个CPU core1  core2都加载了他们,core1更新X,同时,core2更新Y,由于数据的读取和更新是以缓存行为单位的,这就意味着当这2件事同时发生时,就产生了竞争,导致core1和core2有可能需要重新刷新自己的数据(缓存行被对方更新了),这就导致系统的性能大大折扣,这就是伪共享问题。

那怎么改进呢?如下图:

如何用Random生成随机数

上图中,我们把X单独占用一个缓存行,Y单独占用一个缓存行,这样各自更新和读取,都不会有任何影响了。

而上述代码中的@sun.misc.Contended("tlr")就会在虚拟机层面,帮助我们在变量的前后生成一些padding,使得被标注的变量位于同一个缓存行,不与其它变量冲突。

在Thread对象中,成员变量threadLocalRandomSeed,threadLocalRandomProbe,threadLocalRandomSecondarySeed被标记为同一个组tlr,使得这3个变量放置于一个单独的缓存行,而不与其它变量发生冲突,从而提高在并发环境中的访问速度。

反射的高效替代方案

随机数的产生需要访问Thread的threadLocalRandomSeed等成员,但是考虑到类的封装性,这些成员却是包内可见的。

很不幸,ThreadLocalRandom位于java.util.concurrent包,而Thread则位于java.lang包,因此,ThreadLocalRandom并没有办法访问Thread的threadLocalRandomSeed等变量。

这时,Java老鸟们可能就会跳出来说:这算什么,看我的反射大法,不管啥都能抠出来访问一下。

说的不错,反射是一种可以绕过封装,直接访问对象内部数据的方法,但是,反射的性能不太好,并不适合作为一个高性能的解决方案。

有没有什么办法可以让ThreadLocalRandom访问Thread的内部成员,同时又具有远超于反射的,且无限接近于直接变量访问的方法呢?答案是肯定的,这就是使用Unsafe类。

这里,就简单介绍一下用的两个Unsafe的方法:

public native long    getLong(Object o, long offset); public native void    putLong(Object o, long offset, long x);

其中getLong()方法,会读取对象o的第offset字节偏移量的一个long型数据;putLong()则会将x写入对象o的第offset个字节的偏移量中。

这类类似C的操作方法,带来了极大的性能提升,更重要的是,由于它避开了字段名,直接使用偏移量,就可以轻松绕过成员的可见性限制了。

性能问题解决了,那下一个问题是,我怎么知道threadLocalRandomSeed成员在Thread中的偏移位置呢,这就需要用unsafe的objectFieldOffset()方法了,请看下面的代码:

如何用Random生成随机数

上述这段static代码,在ThreadLocalRandom类初始化的时候,就取得了Thread成员变量threadLocalRandomSeed,threadLocalRandomProbe,threadLocalRandomSecondarySeed在对象偏移中的位置。

因此,只要ThreadLocalRandom需要使用这些变量,都可以通过unsafe的getLong()和putLong()来进行访问(也可能是getInt()和putInt())。

比如在生成一个随机数的时候:

protected int next(int bits) {      return (int)(mix64(nextSeed()) >>> (64 - bits));  }  final long nextSeed() {      Thread t; long r; // read and update per-thread seed      //在ThreadLocalRandom中,访问了Thread的threadLocalRandomSeed变量      UNSAFE.putLong(t = Thread.currentThread(), SEED,                     r = UNSAFE.getLong(t, SEED) + GAMMA);      return r;  }

这种Unsafe的方法掉地能有多快呢,让我们一起看做个试验看看:

这里,我们自己写一个ThreadTest类,使用反射和unsafe两种方法,来不停读写threadLocalRandomSeed成员变量,比较它们的性能差异,代码如下:

如何用Random生成随机数

上述代码中,分别使用反射方式byReflection()  和Unsafe的方式byUnsafe()来读写threadLocalRandomSeed变量1亿次,得到的测试结果如下:

byUnsafe spend :171ms byReflection spend :645ms

不难看到,使用Unsafe的方法远远优于反射的方法,这也是JDK内部,大量使用Unsafe来替代反射的原因之一。

随机数种子

我们知道,伪随机数生成都需要一个种子,threadLocalRandomSeed和threadLocalRandomSecondarySeed就是这里的种子。其中threadLocalRandomSeed是long型的,threadLocalRandomSecondarySeed是int。

threadLocalRandomSeed是使用最广泛的大量的随机数其实都是基于threadLocalRandomSeed的。而threadLocalRandomSecondarySeed只是某些特定的JDK内部实现中有使用,使用并不广泛。

初始种子默认使用的是系统时间:

如何用Random生成随机数

上述代码中完成了种子的初始化,并将初始化的种子通过UNSAFE存在SEED的位置(即threadLocalRandomSeed)。

接着就可以使用nextInt()方法获得随机整数了:

public int nextInt() {     return mix32(nextSeed()); }     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; }

每一次调用nextInt()都会使用nextSeed()更新threadLocalRandomSeed。由于这是一个线程独有的变量,因此完全不会有竞争,也不会有CAS的重试,性能也就大大提高了。

探针Probe的作用

除了种子外,还有一个threadLocalRandomProbe探针变量,这个变量是用来做什么的呢?

我们可以把threadLocalRandomProbe  理解为一个针对每个Thread的Hash值(不为0),它可以用来作为一个线程的特征值,基于这个值可以为线程在数组中找到一个特定的位置。

static final int getProbe() {     return UNSAFE.getInt(Thread.currentThread(), PROBE); }

来看一个代码片段:

CounterCell[] as; long b, s; if ((as = counterCells) != null ||     !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {     CounterCell a; long v; int m;     boolean uncontended = true;     if (as == null || (m = as.length - 1) < 0 ||         // 使用probe,为每个线程找到一个在数组as中的位置         // 由于每个线程的probe值不一样,因此大概率 每个线程对应的数组中的元素也是不一样的         // 每个线程对应了不同的元素,就可以没有冲突的进行完全的并发操作         // 因此探针probe在这里 就起到了防止冲突的作用         (a = as[ThreadLocalRandom.getProbe() & m]) == null ||         !(uncontended =           U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

在具体的实现中,如果上述代码发生了冲突,那么,还可以使用ThreadLocalRandom.advanceProbe()方法来修改一个线程的探针值,这样可以进一步避免未来可能得冲突,从而减少竞争,提高并发性能。

static final int advanceProbe(int probe) {     //根据当前探针值,计算一个更新的探针值     probe ^= probe << 13;   // xorshift     probe ^= probe >>> 17;     probe ^= probe << 5;     //更新探针值到线程对象中 即修改了threadLocalRandomProbe变量     UNSAFE.putInt(Thread.currentThread(), PROBE, probe);     return probe; }

“如何用Random生成随机数”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何用Random生成随机数

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

python如何使用random()生成随机数

这篇文章给大家分享的是有关python如何使用random()生成随机数的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。random()生成一个(0,1)指定范围内的随机浮点数运行结果如下:感谢各位的阅读!关于“p
2023-06-08

如何使用random库随机生成浮点数

要使用random库随机生成浮点数,可以使用random模块中的random()函数。此函数返回一个0到1之间的随机浮点数。以下是一个例子,演示如何使用random库生成一个范围在0到1之间的随机浮点数:```pythonimport ra
2023-09-06

java中怎么使用random生成随机数

在Java中可以使用`java.util.Random`类来生成随机数。以下是使用`Random`类生成随机数的示例代码:```javaimport java.util.Random;public class RandomNumberExa
2023-09-06

random中怎么随机生成10个数

这期内容当中小编将会给大家带来有关random中怎么随机生成10个数,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。生成10个随机数import randomnum = range(0, 100) #
2023-06-02

Java中的随机数Random怎么生成

在Java中,可以使用Random类来生成随机数。以下是使用Random类生成随机数的示例代码:```javaimport java.util.Random;public class GenerateRandomNumber {public
2023-08-18

Python中random模块生成随机数详解

Python中的random模块用于生成随机数。下面介绍一下random模块中最常用的几个函数。random.random random.random()用于生成一个0到1的随机符点数: 0 <= n < 1.0random.uniform
2022-06-04

python随机函数random如何使用

Python中的随机函数random可以通过导入random模块来使用。下面是几种常见的使用方法:1. 生成一个0到1之间的随机浮点数:```import randomr = random.random()print(r)```2. 生成一
2023-10-10

php如何生成随机数

这篇文章主要介绍php如何生成随机数,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!php生成随机数的方法非常简单,因为php内部为我们提供了现成的随机数方法。因此我们并不需要进行额外配置,只需要调用内部方法即可。ph
2023-06-06

java随机数如何生成

在Java中,可以使用java.util.Random类来生成随机数。以下是一些示例代码:生成一个范围在0到n之间的随机整数:import java.util.Random;public class RandomNumberGenerato
java随机数如何生成
2024-02-29

shell如何生成随机数

这篇文章主要为大家展示了“shell如何生成随机数”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“shell如何生成随机数”这篇文章吧。 shell 生成随机数的实现方法总结1. 使用系统的 $R
2023-06-09

python随机数如何生成

在Python中,可以使用random模块来生成随机数。以下是几种常见的生成随机数的方法:1. 生成一个0到1之间的随机浮点数,使用random()函数:```import randomrandom_number = random.rand
2023-09-25

Go如何生成随机数组?

package main import ( "fmt" "math/rand" "time" ) func main() { // 4. 随机生成五个数,并将其反转打印。复杂应用 // 思路: // 随机生成五个数,rand.Intn()
2022-06-07

mongodb如何随机生成数据

在 MongoDB 中,可以使用以下方法生成随机数据:1. 使用随机函数生成随机数据。例如,使用 JavaScript 的 Math.random() 函数生成随机数,然后插入到 MongoDB 中。```javascriptdb.coll
2023-08-31

编程热搜

目录