概述
java.util.random可以產生int、long、float、double以及goussian等類型的隨機數。這也是它與java.lang.math中的方法random()最大的不同之處,后者只產生double型的隨機數。
該類的實例被用于生成偽隨機數的流。該類使用一個 48 位的種子,它被一個線性同余公式所修改。如果 random 的兩個實例用同一種子創建,對每個實例完成同方法調用序列它們將生成和返回相同的數序列成同一方法調用序列,它們將生成和返回相同的數序列。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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( "" ); } } |
輸出結果:
從結果中發現,只要種子一樣,獲取的隨機數的序列就是一致的。是一種偽隨機數的實現,而不是真正的隨機數。
random 源碼分析
random 類結構
1
2
3
4
5
6
7
|
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); |
有參構造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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; } |
通過傳入一個種子,來生成隨機數,通過上面的例子發現,種子一樣產生的隨機數序列一樣,如果每次使用想產生不一樣的序列,那就只能每次傳入一個不一樣的種子。
無參構造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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; } } |
通過源碼發現,無參的構造方法,里面幫我們自動產生了一個種子,并通過cas自旋方式保證,每次獲取的種子不一樣,從而保證每次new random()
獲取的隨機序列不一致。
nextint() 方法:獲取 int 隨機數
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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)); } |
從代碼中我們可以發現,只要種子確定后,每次產生的數,都是采用固定的算法進行產生的,所以只要種子確定后,每次產生的序列就是固定的。
每次更新種子的時候是使用的cas來更新的,如果高并發的環境下,性能是個問題。
安全性問題
試想下,如果這是一個搖獎平臺,只要種子確定后,每次產生的序列都一樣。這樣就可利用這個漏洞來預測下一次開獎的號碼,這樣容易被一些人鉆空子。
jdk建議大家盡量要使用 securerandom 來實現隨機數的生成。
securerandom
securerandom是強隨機數生成器,主要應用的場景為:用于安全目的的數據數,例如生成秘鑰或者會話標示(session id),在上文《偽隨機數安全性》中,已經給大家揭露了弱隨機數生成器的安全問題,而使用securerandom這樣的強隨機數生成器將會極大的降低出問題的風險。
產生高強度的隨機數,有兩個重要的因素:種子和算法。算法是可以有很多的,通常如何選擇種子是非常關鍵的因素。 如random,它的種子是system.currenttimemillis(),所以它的隨機數都是可預測的, 是弱偽隨機數。
強偽隨機數的生成思路:收集計算機的各種信息,鍵盤輸入時間,內存使用狀態,硬盤空閑空間,io延時,進程數量,線程數量等信息,cpu時鐘,來得到一個近似隨機的種子,主要是達到不可預測性。
說的簡單點就是,使用加密算法生成很長的一個隨機種子,讓你無法猜測出種子,也就無法推導出隨機序列數。
random性能問題
從 random 源碼中我們發現,每次獲取隨機數的時候都是使用cas的方式進行更新種子的值。這樣在高并發的環境中會存在大量的cas重試,導致性能下降。這時建議大家使用threadlocalrandom類來實現隨機數的生成。
threadlocalrandom 實現原理
thread 類
thread 類中有一個 threadlocalrandomseed 屬性。
threadlocalrandom 結構
seed 變量是 threadlocalrandomseed 在 thread 對象中的偏移量。
threadlocalrandom.nextseed() 方法
從這個方法中,我們發現,每個線程的種子值都存儲在thread對象的threadlocalrandomseed 屬性中。
結論
因為threadlocalrandom 中的種子存儲在thread對象中,所以高并發獲取random對象時,不會使用cas來保證每次獲取的值不一致。
每個線程維護一個它自己的種子,每個線程需要獲取隨機數的時候,從當前的thread對象中獲取當前線程的種子,進行獲取隨機數,性能大大提高。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:http://www.jianshu.com/p/ab0c15c6dd99