当前位置: 首页 > 后端技术 > Java

java并发高的情况下使用ThreadLocalRandom生成随机数

时间:2023-04-02 10:18:24 Java

性能一:简介如果我们要生成一个随机数,我们通常使用Random类。但是Random在并发条件下生成随机数的性能不是很理想。今天介绍一下JUC包中用来生成随机数的类——ThreadLocalRandom。(本文基于JDK1.8)二:Random性能较差WhereRandom随机数生成与seed种子有关,为了保证线程安全,Random使用了CAS机制来保证线程安全。从next()方法中我们可以发现seed是通过自旋锁和CAS修改值的。如果是在高并发场景下,可能会导致CAS不断失败,导致不断自旋,从而可能导致服务器CPU过高。protectedintnext(intbits){longoldseed,nextseed;AtomicLongseed=this.seed;做{oldseed=seed.get();nextseed=(oldseed*multiplier+addend)&mask;}while(!seed.compareAndSet(oldseed,nextseed));return(int)(nextseed>>>(48-bits));}三:ThreadLocalRandom的简单使用使用的方法很简单,通过ThreadLocalRandom.current()获取ThreadLocalRandom实例,然后通过nextInt()、nextLong()等方法获取一个随机数。代码:@Testvoidtest()throwsInterruptedException{newThread(()->{ThreadLocalRandomrandom=ThreadLocalRandom.current();System.out.println(random.nextInt(100));}).start();新线程(()->{ThreadLocalRandomrandom=ThreadLocalRandom.current();System.out.println(random.nextInt(100));}).start();Thread.sleep(100);}运行结果:四:为什么ThreadLocalRandom可以在保证线程安全的情况下有很好的性能。我们可以看一下ThreadLocalRandom的代码实现。首先,我们很容易看出这是一个饥饿的单例/**仅用于静态单例的构造函数*/privateThreadLocalRandom(){initialized=true;//在super()调用期间为false}/**ThecommonThreadLocalRandom*/staticfinalThreadLocalRandominstance=newThreadLocalRandom();我们可以看到PROBE成员变量表示Thread类的threadLocalRandomProbe属性的内存偏移量,SEED成员变量表示Thread类的threadLocalRandomSeed属性的内存偏移量。SECONDARY成员变量表示Thread类的threadLocalRandomSecondarySeed属性的内存偏移量。//不安全机制privatestaticfinalsun.misc.UnsafeUNSAFE;privatestaticfinallongSEED;privatestaticfinallongPROBE;privatestaticfinallongSECONDARY;static{try{UNSAFE=sun.misc.Unsafe.getUnsafe();>tk=线程类;SEED=UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));PROBE=UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));SECONDARY=UNSAFE.objectFieldOffset("lartkFieldDecthreadLocalRandomSecondarySeed"));}catch(Exceptione){thrownewError(e);}}可以看到Thread类确实有这三个属性Thread类:@sun.misc.Contended("tlr")//CurrentThread的随机种子默认值为0longthreadLocalRandomSeed;/**探测哈希值;nonzeroifthreadLocalRandomSeedinitialized*/@sun.misc.Contended("tlr")//用于标记当前Thread的threadLocalRandomSeed是否已经初始化。0表示没有,非零表示已经初始化,默认值为0intthreadL局部随机探针;/**SecondaryseedisolatedfrompublicThreadLocalRandomsequence*/@sun.misc.Contended("tlr")//当前Thread的secondaryrandomSeed默认值为0intthreadLocalRandomSecondarySeed;接下来我们看ThreadLocalRandom.current()方法ThreadLocalRandom.current()ThreadLocalRandom.current()的作用主要是初始化随机种子,返回一个ThreadLocalRandom的实例。首先通过UNSAFE类获取当前线程的Thread对象的threadLocalRandomProbe属性,查看随机种子是否已经初始化。如果没有初始化,则调用localInit()方法初始化publicstaticThreadLocalRandomcurrent(){//获取当前线程if(UNSAFE.getInt(Thread.currentThread(),PROBE)==0)localInit();返回实例;}localInit()localInit()方法的作用是初始化随机种子。可以看到代码很简单,就是通过UNSAFE类给当前Thread的threadLocalRandomProbe属性和threadLocalRandomSeed属性赋值。staticfinalvoidlocalInit(){intp=probeGenerator.addAndGet(PROBE_INCREMENT);int探针=(p==0)?1:p;//跳过0长种子=mix64(seeder.getAndAdd(SEEDER_INCREMENT));线程t=Thread.currentThread();UNSAFE.putLong(t,种子,种子);UNSAFE.putInt(t,PROBE,probe);下面以nextInt()方法为例,看看ThreadLocalRandom是如何产生随机数的。我们可以看到随机数是通过nextSeed()方法获取的,然后通过随机种子生成的。所以关注下nextSeed()方法是如何获取随机种子的。publicintnextInt(intbound){if(bound<=0)thrownewIllegalArgumentException(BadBound);intr=mix32(nextSeed());intm=bound-1;if((bound&m)==0)//两个r&=m的幂;else{//拒绝代表过高的候选人for(intu=r>>>1;u+m-(r=u%bound)<0;u=mix32(nextSeed())>>>1);}返回r;}nextSeed()nextSeed()方法的作用是获取一个随机种子。代码很简单,就是通过UNSAFE类获取当前线程的threadLocalRandomSeed属性,将原来的threadLocalRandomSeed加上GAMMA设置为新的threadLocalRandomSeed。finallongnextSeed(){线程t;长r;//读取和更新每线程种子UNSAFE.putLong(t=Thread.currentThread(),SEED,r=UNSAFE.getLong(t,SEED)+GAMMA);返回r;总结:为什么ThreadLocalRandom是线程安全的?是因为它在当前Thread对象的threadLocalRandomSeed变量中保存了随机种子,使得每个线程都有自己的随机种子,实现了线程级的隔离,所以ThreadLocalRandom不需要像RandomThread那样通过自旋锁和cas来保证随机种子的安全性。在高并发场景下,效率会比较高。注意:有没有发现ThreadLocalRandom保证线程安全的方式有点像ThreadLocal?注意点:1.ThreadLocalRandom是单例。2、我们每个线程在获取随机数之前都需要调用ThreadLocalRandom.current()来初始化当前线程的随机种子。3、理解ThreadLocalRandom需要理解UnSafe类,它是Java提供的一个工具类,可以直接通过内存获取和修改变量。Java的CAS也是通过这个工具类实现的。

猜你喜欢