您好,登錄后才能下訂單哦!
這篇文章給大家介紹java.util.Random的原理是什么,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
java.util.Random可以產(chǎn)生int、long、float、double以及Goussian等類型的隨機數(shù)。這也是它與java.lang.Math中的方法Random()最大的不同之處,后者只產(chǎn)生double型的隨機數(shù)。
該類的實例被用于生成偽隨機數(shù)的流。該類使用一個 48 位的種子,它被一個線性同余公式所修改。如果 Random 的兩個實例用同一種子創(chuàng)建,對每個實例完成同方法調(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),只要種子一樣,獲取的隨機數(shù)的序列就是一致的。是一種偽隨機數(shù)的實現(xiàn),而不是真正的隨機數(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; }
通過傳入一個種子,來生成隨機數(shù),通過上面的例子發(fā)現(xiàn),種子一樣產(chǎn)生的隨機數(shù)序列一樣,如果每次使用想產(chǎn)生不一樣的序列,那就只能每次傳入一個不一樣的種子。
無參構(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; } }
通過源碼發(fā)現(xiàn),無參的構(gòu)造方法,里面幫我們自動產(chǎn)生了一個種子,并通過CAS自旋方式保證,每次獲取的種子不一樣,從而保證每次new Random()
獲取的隨機序列不一致。
nextInt() 方法:獲取 int 隨機數(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ù),都是采用固定的算法進行產(chǎn)生的,所以只要種子確定后,每次產(chǎn)生的序列就是固定的。
每次更新種子的時候是使用的CAS來更新的,如果高并發(fā)的環(huán)境下,性能是個問題。
安全性問題
試想下,如果這是一個搖獎平臺,只要種子確定后,每次產(chǎn)生的序列都一樣。這樣就可利用這個漏洞來預(yù)測下一次開獎的號碼,這樣容易被一些人鉆空子。
jdk建議大家盡量要使用 SecureRandom 來實現(xiàn)隨機數(shù)的生成。
SecureRandom
SecureRandom是強隨機數(shù)生成器,主要應(yīng)用的場景為:用于安全目的的數(shù)據(jù)數(shù),例如生成秘鑰或者會話標示(session ID),在上文《偽隨機數(shù)安全性》中,已經(jīng)給大家揭露了弱隨機數(shù)生成器的安全問題,而使用SecureRandom這樣的強隨機數(shù)生成器將會極大的降低出問題的風(fēng)險。
產(chǎn)生高強度的隨機數(shù),有兩個重要的因素:種子和算法。算法是可以有很多的,通常如何選擇種子是非常關(guān)鍵的因素。 如Random,它的種子是System.currentTimeMillis(),所以它的隨機數(shù)都是可預(yù)測的, 是弱偽隨機數(shù)。
強偽隨機數(shù)的生成思路:收集計算機的各種信息,鍵盤輸入時間,內(nèi)存使用狀態(tài),硬盤空閑空間,IO延時,進程數(shù)量,線程數(shù)量等信息,CPU時鐘,來得到一個近似隨機的種子,主要是達到不可預(yù)測性。
說的簡單點就是,使用加密算法生成很長的一個隨機種子,讓你無法猜測出種子,也就無法推導(dǎo)出隨機序列數(shù)。
Random性能問題
從 Random 源碼中我們發(fā)現(xiàn),每次獲取隨機數(shù)的時候都是使用CAS的方式進行更新種子的值。這樣在高并發(fā)的環(huán)境中會存在大量的CAS重試,導(dǎo)致性能下降。這時建議大家使用ThreadLocalRandom類來實現(xiàn)隨機數(shù)的生成。
ThreadLocalRandom 實現(xiàn)原理
Thread 類
Thread 類中有一個 threadLocalRandomSeed 屬性。
ThreadLocalRandom 結(jié)構(gòu)
SEED 變量是 threadLocalRandomSeed 在 Thread 對象中的偏移量。
ThreadLocalRandom.nextSeed() 方法
從這個方法中,我們發(fā)現(xiàn),每個線程的種子值都存儲在Thread對象的threadLocalRandomSeed 屬性中。
結(jié)論
因為ThreadLocalRandom 中的種子存儲在Thread對象中,所以高并發(fā)獲取Random對象時,不會使用CAS來保證每次獲取的值不一致。
每個線程維護一個它自己的種子,每個線程需要獲取隨機數(shù)的時候,從當前的Thread對象中獲取當前線程的種子,進行獲取隨機數(shù),性能大大提高。
關(guān)于java.util.Random的原理是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。