首页 > 编程知识 正文

js随机数生成1到100,rand每次产生的随机数都一样

时间:2023-05-06 00:11:38 阅读:49383 作者:4021

赞看,养成习惯,微信搜索【三太子拉长的小懒虫】关注这个有点像的笨蛋

本文http://www.Sina.com/https://github.com/Java family已经收录,有一线大厂面试的完整考点、资料、简历模板,还有我的程序人生。

前言在代码中生成随机数是非常常见的功能。 此外,JDK已经提供了现成的Random类来实现这一点,Random类是线程安全的。

以下是Random.next )生成随机整数的实现。

保护性字节(int bits ) { long oldseed,nextseed; AtomicLong seed=this.seed; do { oldseed=seed.get (; nextseed=(old seed * multiplier addend )掩码; //CAS有竞争的是低效率(} while (! seed.compareandset(oldseed,nextseed ); 返回(int ) )下一个(48位); }上述方法使用CAS操作更新了seed。 在许多线程冲突的情况下,此CAS操作很可能失败,如果失败,将重试。 这种重试会消耗CPU运算,导致性能大幅下降。

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

为了改善这一问题,改进了高并发环境中的随机数生成器的性能,导致了高性能的高并发随机数生成器ThreadLocalRandom——。

ThreadLocalRandom继承自Random,根据里希特置换原则,ThreadLocalRandom提供了与Random相同的随机数生成功能,但表示实现算法略有不同。

为了使Thread变量能够解决线程冲突,Java有一个ThreadLocal类,为每个线程分配了独立且相互无关的存储空间。

ThreadLocal的实现取决于Thread对象的thread local.threadlocalmapthreadlocals成员字段。

同样,为了使随机数生成器仅访问本地线程数据以避免竞争,Thread中添加了三个成员。

/* * thecurrentseedforathreadlocalrandom */@ sun.misc.contended (' TLR ' ) long threadLocalRandomSeed; /** Probe hash value; nonzeroifthreadlocalrandomseedinitialized */@ sun.misc.contended (' TLR ' ) int threadLocalRandomProbe;/* * secondaryseedisolatedfrompublicthreadlocalrandomsequence */@ sun.misc.contended (' TLR ' ) intthreadlocalrandomsed 由于自然地与各个Thread对象紧密地捆绑在一起,所以它是名副其实的ThreadLocal变量,依赖于这些变量实现的随机数生成器也是ThreadLocalRandom。

您可能注意到消除了伪共享,这些变量上面有@sun.misc.Contended的注释。 这个评论是用来做什么的? 要了解这一点,请访问并发编程中的关键问题之一—— GitHub

我们知道,CPU不直接访问内存,所有数据都从缓存加载到寄存器中,缓存有L1、L2、L3等级别。 在此,我们首先简化这些负责的层次结构关系,假设只有一级缓存和一个主内存。

CPU读取和更新缓存时,以行为单位进行。 也称为cache line,一行通常为64字节,即长长度。

那里会发生问题。 一个缓存行可以包含多个变量。 如果多个线程同时访问的不同变量,而这些不同的变量正好位于同一高速缓存行中,会发生什么呢?

如上图所示,x和y是两个相邻变量,它们位于同一缓存线中,两个CPU core1 core2加载它们,core1更新x,同时core2更新y。 由于数据的读取和更新是以高速缓存线为单位进行的,因此如果这两件事同时发生,则可能会发生冲突,从而重新执行酷睿1和酷睿2

怎么改善? 下图:

在上图中,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()方法了,请看下面的代码:

上述这段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成员变量,比较它们的性能差异,代码如下:

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

byUnsafe spend :171msbyReflection spend :645ms

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

随机数种子

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

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

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

上述代码中完成了种子的初始化,并将初始化的种子通过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; } 总结

今天,我们介绍了ThreadLocalRandom对象,这是一个高并发环境中的,高性能的随机数生成器。

我们不但介绍了ThreadLocalRandom的功能和内部实现原理,还介绍介绍了ThreadLocalRandom对象是如何达到高性能的(比如通过伪共享,Unsafe等手段),希望大家可以将这些技术灵活运用到自己的工程中。

小傻瓜们对这个冷门类是否有深一步的理解了?理解了可以在评论区来一波:变得更强

我是拉长的小懒虫,你知道的越多,不知道的越多,我们下期见。

拉长的小懒虫把自己的面试文章整理成了一本电子书,共 1630页!

干货满满,字字精髓。目录如下,有我复习时总结的面试题以及简历模板,现在免费送给大家。

链接:https://pan.baidu.com/s/1ZQEKJBgtYle3v-1LimcSwg 密码:wjk6

文章持续更新,可以微信搜一搜「 三太子拉长的小懒虫 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。