Java中生成隨機(jī)數(shù)Random VS ThreadLocalRandom性能比較
?前言
大家項(xiàng)目中如果有生成隨機(jī)數(shù)的需求,我想大多都會(huì)選擇使用Random來(lái)實(shí)現(xiàn),它內(nèi)部使用了CAS來(lái)實(shí)現(xiàn)。實(shí)際上,JDK1.7之后,提供了另外一個(gè)生成隨機(jī)數(shù)的類ThreadLocalRandom,那么他們二者之間的性能是怎么樣的呢?
Random的使用
Random類是JDK提供的生成隨機(jī)數(shù)的類, 這個(gè)類不是隨機(jī)的,而是偽隨機(jī)的。什么是偽隨機(jī)呢?偽隨機(jī)是指生成的隨機(jī)數(shù)是有一定規(guī)律的,這個(gè)規(guī)律出現(xiàn)的周期因偽隨機(jī)算法的優(yōu)劣而異。一般來(lái)說(shuō),周期比較長(zhǎng),但可以預(yù)見(jiàn)。我們可以通過(guò)以下代碼簡(jiǎn)單地使用 Random:
Random中有很多方法。這里我們就分析比較常見(jiàn)的nextInt()和nextInt(int bound)方法。
- nextInt()會(huì)計(jì)算int范圍內(nèi)的隨機(jī)數(shù),
- nextInt(int bound)會(huì)計(jì)算[0,bound) 之間的隨機(jī)數(shù),左閉右開(kāi)。
實(shí)現(xiàn)原理
Random類的構(gòu)造函數(shù)如下圖所示:
這里面直接調(diào)用了next()方法,傳入了32,這里的32是指Int的位數(shù)。
這里會(huì)根據(jù)seed的當(dāng)前值,通過(guò)一定的規(guī)則(偽隨機(jī))計(jì)算出下一個(gè)seed,然后進(jìn)行CAS。如果CAS失敗,繼續(xù)循環(huán)上述操作。最后根據(jù)我們需要的位數(shù)返回。
小結(jié):可以看出在next(int bits)?方法中,對(duì)AtomicLong進(jìn)行了CAS操作,如果失敗則循環(huán)重試。很多人一看到CAS,因?yàn)椴恍枰渔i,第一時(shí)間就想到了高性能、高并發(fā)。但是在這里,卻成為了我們多線程并發(fā)性能的瓶頸??梢韵胂?,當(dāng)我們有多個(gè)線程執(zhí)行CAS時(shí),只有一個(gè)線程一定會(huì)失敗,其他的會(huì)繼續(xù)循環(huán)執(zhí)行CAS操作。當(dāng)并發(fā)線程較多時(shí),性能就會(huì)下降。
ThreadLocalRandom的使用
JDK1.7之后,提供了一個(gè)新類ThreadLocalRandom?來(lái)替代Random。
實(shí)現(xiàn)原理
我們先來(lái)看下current()方法。
如果沒(méi)有初始化,先進(jìn)行初始化,這里我們的seed不再是全局變量了。我們的線程中有三個(gè)變量:
- threadLocalRandomSeed:這是我們用來(lái)控制隨機(jī)數(shù)的種子。
- threadLocalRandomProbe:這個(gè)就是ThreadLocalRandom,用來(lái)控制初始化。
- threadLocalRandomSecondarySeed:這是二級(jí)種子。
關(guān)鍵代碼如下:
可以看出,由于每個(gè)線程都維護(hù)自己的seed?,所以此時(shí)不需要CAS?,直接進(jìn)行put。這里通過(guò)線程間的隔離來(lái)減少并發(fā)沖突,所以ThreadLocalRandom的性能非常高。
性能對(duì)比
通過(guò)基準(zhǔn)工具JMH測(cè)試:
運(yùn)行結(jié)果如下圖所示,最左邊是并發(fā)線程的數(shù)量:
顯而易見(jiàn),無(wú)論線程數(shù)量是多少,ThreadLocalRandom?性能是遠(yuǎn)高于Random。
總結(jié)
本文講解了JDK中提供的兩種生成隨機(jī)數(shù)的方式,一個(gè)是JDK 1.0引入的Random?類,另外一個(gè)是JDK1.7引入的ThreadLocalRandom?類,由于底層的實(shí)現(xiàn)機(jī)制不同,ThreadLocalRandom?的性能是遠(yuǎn)高于Random?,建議后面大家在技術(shù)選型的時(shí)候優(yōu)先使用ThreadLocalRandom。