關(guān)于java.util.Random的實(shí)現(xiàn)原理詳解
概述
java.util.Random可以產(chǎn)生int、long、float、double以及Goussian等類型的隨機(jī)數(shù)。這也是它與java.lang.Math中的方法Random()最大的不同之處,后者只產(chǎn)生double型的隨機(jī)數(shù)。
該類的實(shí)例被用于生成偽隨機(jī)數(shù)的流。該類使用一個(gè) 48 位的種子,它被一個(gè)線性同余公式所修改。如果 Random 的兩個(gè)實(shí)例用同一種子創(chuàng)建,對(duì)每個(gè)實(shí)例完成同方法調(diào)用序列它們將生成和返回相同的數(shù)序列成同一方法調(diào)用序列,它們將生成和返回相同的數(shù)序列。
示例
public class RandomTest {
public static void main(String[] args) {
testRandom();
System.out.println("---------------------");
testRandom();
System.out.println("---------------------");
testRandom();
}
public static void testRandom(){
Random random = new Random(1);
for(int i=0; i<5; i++){
System.out.print(random.nextInt()+"\t");
}
System.out.println("");
}
}
輸出結(jié)果:

從結(jié)果中發(fā)現(xiàn),只要種子一樣,獲取的隨機(jī)數(shù)的序列就是一致的。是一種偽隨機(jī)數(shù)的實(shí)現(xiàn),而不是真正的隨機(jī)數(shù)。
Random 源碼分析
Random 類結(jié)構(gòu)
class Random implements java.io.Serializable {
private final AtomicLong seed;
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
有參構(gòu)造方法
public Random(long seed) {
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
// subclass might have overriden setSeed
this.seed = new AtomicLong();
setSeed(seed);
}
}
private static long initialScramble(long seed) {
return (seed ^ multiplier) & mask;
}
通過(guò)傳入一個(gè)種子,來(lái)生成隨機(jī)數(shù),通過(guò)上面的例子發(fā)現(xiàn),種子一樣產(chǎn)生的隨機(jī)數(shù)序列一樣,如果每次使用想產(chǎn)生不一樣的序列,那就只能每次傳入一個(gè)不一樣的種子。
無(wú)參構(gòu)造方法
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
通過(guò)源碼發(fā)現(xiàn),無(wú)參的構(gòu)造方法,里面幫我們自動(dòng)產(chǎn)生了一個(gè)種子,并通過(guò)CAS自旋方式保證,每次獲取的種子不一樣,從而保證每次new Random()獲取的隨機(jī)序列不一致。
nextInt() 方法:獲取 int 隨機(jī)數(shù)
public int nextInt() {
return next(32);
}
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));
}
從代碼中我們可以發(fā)現(xiàn),只要種子確定后,每次產(chǎn)生的數(shù),都是采用固定的算法進(jìn)行產(chǎn)生的,所以只要種子確定后,每次產(chǎn)生的序列就是固定的。
每次更新種子的時(shí)候是使用的CAS來(lái)更新的,如果高并發(fā)的環(huán)境下,性能是個(gè)問(wèn)題。
安全性問(wèn)題
試想下,如果這是一個(gè)搖獎(jiǎng)平臺(tái),只要種子確定后,每次產(chǎn)生的序列都一樣。這樣就可利用這個(gè)漏洞來(lái)預(yù)測(cè)下一次開(kāi)獎(jiǎng)的號(hào)碼,這樣容易被一些人鉆空子。
jdk建議大家盡量要使用 SecureRandom 來(lái)實(shí)現(xiàn)隨機(jī)數(shù)的生成。
SecureRandom
SecureRandom是強(qiáng)隨機(jī)數(shù)生成器,主要應(yīng)用的場(chǎng)景為:用于安全目的的數(shù)據(jù)數(shù),例如生成秘鑰或者會(huì)話標(biāo)示(session ID),在上文《偽隨機(jī)數(shù)安全性》中,已經(jīng)給大家揭露了弱隨機(jī)數(shù)生成器的安全問(wèn)題,而使用SecureRandom這樣的強(qiáng)隨機(jī)數(shù)生成器將會(huì)極大的降低出問(wèn)題的風(fēng)險(xiǎn)。
產(chǎn)生高強(qiáng)度的隨機(jī)數(shù),有兩個(gè)重要的因素:種子和算法。算法是可以有很多的,通常如何選擇種子是非常關(guān)鍵的因素。 如Random,它的種子是System.currentTimeMillis(),所以它的隨機(jī)數(shù)都是可預(yù)測(cè)的, 是弱偽隨機(jī)數(shù)。
強(qiáng)偽隨機(jī)數(shù)的生成思路:收集計(jì)算機(jī)的各種信息,鍵盤輸入時(shí)間,內(nèi)存使用狀態(tài),硬盤空閑空間,IO延時(shí),進(jìn)程數(shù)量,線程數(shù)量等信息,CPU時(shí)鐘,來(lái)得到一個(gè)近似隨機(jī)的種子,主要是達(dá)到不可預(yù)測(cè)性。
說(shuō)的簡(jiǎn)單點(diǎn)就是,使用加密算法生成很長(zhǎng)的一個(gè)隨機(jī)種子,讓你無(wú)法猜測(cè)出種子,也就無(wú)法推導(dǎo)出隨機(jī)序列數(shù)。
Random性能問(wèn)題
從 Random 源碼中我們發(fā)現(xiàn),每次獲取隨機(jī)數(shù)的時(shí)候都是使用CAS的方式進(jìn)行更新種子的值。這樣在高并發(fā)的環(huán)境中會(huì)存在大量的CAS重試,導(dǎo)致性能下降。這時(shí)建議大家使用ThreadLocalRandom類來(lái)實(shí)現(xiàn)隨機(jī)數(shù)的生成。
ThreadLocalRandom 實(shí)現(xiàn)原理
Thread 類

Thread 類中有一個(gè) threadLocalRandomSeed 屬性。
ThreadLocalRandom 結(jié)構(gòu)

SEED 變量是 threadLocalRandomSeed 在 Thread 對(duì)象中的偏移量。
ThreadLocalRandom.nextSeed() 方法

從這個(gè)方法中,我們發(fā)現(xiàn),每個(gè)線程的種子值都存儲(chǔ)在Thread對(duì)象的threadLocalRandomSeed 屬性中。
結(jié)論
因?yàn)門hreadLocalRandom 中的種子存儲(chǔ)在Thread對(duì)象中,所以高并發(fā)獲取Random對(duì)象時(shí),不會(huì)使用CAS來(lái)保證每次獲取的值不一致。
每個(gè)線程維護(hù)一個(gè)它自己的種子,每個(gè)線程需要獲取隨機(jī)數(shù)的時(shí)候,從當(dāng)前的Thread對(duì)象中獲取當(dāng)前線程的種子,進(jìn)行獲取隨機(jī)數(shù),性能大大提高。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
java中FileOutputStream中文亂碼問(wèn)題解決辦法
這篇文章主要介紹了java中FileOutputStream中文亂碼問(wèn)題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-04-04
使用Java語(yǔ)言將XML轉(zhuǎn)為PDF的方法
這篇文章主要介紹了使用Java語(yǔ)言將XML轉(zhuǎn)為PDF的方法,本文將介紹通過(guò)Java代碼來(lái)實(shí)現(xiàn)該格式轉(zhuǎn)換的方法,需要的朋友可以參考下2022-03-03
Spring指定bean在哪個(gè)應(yīng)用加載(示例詳解)
本文通過(guò)實(shí)例代碼介紹了Spring指定bean在哪個(gè)應(yīng)用加載,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-08-08
SpringBoot注冊(cè)Filter的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot注冊(cè)Filter的兩種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
關(guān)于jpa中無(wú)法刪除onetomany中many問(wèn)題的解決
這篇文章主要介紹了關(guān)于jpa中無(wú)法刪除onetomany中many問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
DecimalFormat數(shù)字格式化 0和# 的區(qū)別及說(shuō)明
這篇文章主要介紹了DecimalFormat數(shù)字格式化 0和# 的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10

