一聲嘆息,jdk竟然有4個random
小姐姐味道【ID:xjjdog】
作者:十年架構,日百億流量經驗,與你分享。
我們從jdk8說起。主要是四個隨機數生成器。神馬?有四個?
接下來我們簡單說下這幾個類的使用場景,來了解其中的細微差別,和api設計者的良苦用心。
java.util.Random
java.util.concurrent.ThreadLocalRandom
java.security.SecureRandom
java.util.SplittableRandom
Random
最常用的就是Random。
用來生成偽隨機數
,預設使用48
位種子、線性同餘公式
進行修改。我們可以透過構造器傳入初始seed
,或者透過setSeed重置(同步)。預設seed為系統時間的納秒數,真大!
如果兩個(多個)不同的Random例項,使用相同的seed,按照相同的順序呼叫相同方法,那麼它們得到的數字序列也是相同的。這看起來不太隨機。 這種設計策略,既有優點也有缺點,優點是“相同seed”生成的序列是一致的,使過程具有可回溯和校驗性(平臺無關、執行時機無關);缺點就是,這種一致性,潛在引入其“可被預測”的風險。
Random的例項是執行緒安全的。 但是,跨執行緒併發使用相同的java.util.Random例項可能會遇到爭用,從而導致效能稍欠佳(nextX方法中,在對seed賦值時使用了CAS,測試結果顯示,其實效能損耗很小)。請考慮在多執行緒設計中使用ThreadLocalRandom。同時,我們在併發環境下,也沒有必要刻意使用多個Random例項。
Random例項不具有加密安全性。 相反,請考慮使用SecureRandom來獲取加密安全的偽隨機數生成器,以供安全敏感應用程式使用。
Random是最常用的隨機數生成類,適用於絕大部分場景。
Random random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));
random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));
random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));
上述三個不同的random例項,使用了相同的seed。呼叫過程一樣,其中產生的隨機數序列也是完全一樣的。多次執行結果也完全一致,簡單而言,只要初始seed一樣,即使例項不同,多次執行它們的結果都是一致的。這個現象與上面所說的一致。
如果Random構造器中不指定seed,而是使用預設的系統時間納秒數作為主導變數,三個random例項執行的結果是不同的。多次執行結果也不一樣。由此可見,seed是否具有隨機性,在一定程度上,也決定了Random產生結果的隨機性。
所以,在分散式或者多執行緒環境下,如果Random例項處於程式碼一致的tasks執行緒中,可能這些分散式程式或者執行緒,產出的序列值是一樣的。這也是在JDK 7引入ForkJoin的同時,也引入了ThreadLocalRandom類。
ThreadLocalRandom
這個類的作用,使得隨機數的生成器隔離到當前執行緒。此類繼承自java.util.Random,與Math類使用的全域性Random生成器一樣,ThreadLocalRandom使用內部生成的種子進行初始化,否則可能無法修改。
在併發程式中使用ThreadLocalRandom,通常會有更少的開銷和競爭。當多個任務(例如,每個ForkJoinTask)線上程池中並行使用隨機數時,ThreadLocalRandom是特別合適的。
切記,在多個執行緒中不應該共享ThreadLocalRandom例項。
ThreadLocalRandom初始化是private的,所以無法透過構造器設定seed,此外其setSeed方法也被重寫而不支援(丟擲異常)。預設情況下,每個ThreadLocalRandom例項的seed主導變數值為系統時間(納秒):
private static long initialSeed() {
String sec = VM.getSavedProperty("java.util.secureRandomSeed");
if (Boolean.parseBoolean(sec)) {
byte[] seedBytes = java.security.SecureRandom.getSeed(8);
long s = (long)(seedBytes[0]) & 0xffL;
for (int i = 1; i < 8; ++i)
s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
return s;
}
return (mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));
}
根據其初始化seed的實現,我們也可以透過JVM啟動引數增加“-Djava.util.secureRandomSeed=true”,此時初始seed變數將不再是系統時間,而是由SecureRandom類生成一個隨機因子,以此作為ThreadLoalRandom的初始seed。
真是夠繞的。
從原始碼中,我並沒有看到Thread-ID作為變數生成seed,而且nextX方法中隨機數生成演算法也具有一致性。這意味著,如果多個執行緒初始ThreadLocalRandom的時間完全一致,在呼叫方法和過程相同的情況下,產生的隨機序列也是相同的;在一定程度上“-Djava.util.secureRandom=true”可以規避此問題。
ThreadLocalRandom並沒有使用ThreadLocal來支援內部資料儲存等,而是直接使用UnSafe操作當前Thread物件引用中seed屬性的記憶體地址並進行資料操作,我比較佩服SUN的這種巧妙的做法。
SecureRandom
它也繼承自Random,該類提供加密強隨機數生成器(RNG),加密強隨機數最低限度符合FIPS 140-2“加密模組的安全要求”。此外,SecureRandom必須產生非確定性輸出。因此,傳遞給SecureRandom物件的任何種子材料必須是不可預測的,並且所有SecureRandom輸出序列必須具有加密強度。(官文,其實我也一知半解)
SecureRandom預設支援兩種RNG加密演算法實現:
”SHA1PRNG”演算法提供者sun.security.provider.SecureRandom
”NativePRNG”提供者sun.security.provider.NativePRNG
預設情況下,是“SHA1PRNG”,即SUN提供的實現。此外可以透過
“-Djava.security=file:/dev/urandom”
(推薦)或者
“-Djava.security=file:/dev/random”
指定使用linux本地的隨機演算法,
即NativePRNG;
其中“/dev/random”與“/dev/urandom”在不同unix-*平臺中實現有所不同,效能也有所差異,建議使用“/dev/urandom”。
/dev/random的一個副本是/dev/urandom (”unlocked”,非阻塞的隨機數發生器),它會重複使用熵池中的資料以產生偽隨機資料。這表示對/dev/urandom的讀取操作不會產生阻塞,但其輸出的熵可能小於/dev/random的。它可以作為生成較低強度密碼的偽隨機數生成器,不建議用於生成高強度長期密碼。
演算法的內部實現,比較複雜;本人測試,其實效能差不不太大(JDK 8環境)。SecureRandom也是執行緒安全的。
從輸出結果上分析,無論是否指定SecureRandom的初始seed,單個例項多次執行的結果也完全不同 ;多個不同的SecureRandom例項無論是否指定seed,即使指定一樣的初始seed,同時執行的結果也完全不同。
SecureRandom繼承自Random,但是對nextX方法中的底層方法進行的重寫覆蓋,不過仍然基於Random的CAS且SecureRandom的底層方法還使用的同步,所以在併發環境下,效能比Random差了一些。
SplittableRandom
JDK 8 新增的API,主要適用於Fork/join形式的跨執行緒操作中。它並沒有繼承java.util.Random類。
具有相同seed的不同SplittableRandom例項或者同一個SplittableRandom,多次執行結果是一致的。這和Random是一致的。
非執行緒安全,不能被併發使用。 (不會報錯,但是併發時可能多個執行緒同時得到相同的隨機數)
同ThreadLocalRandom,對“-Djava.util.secureRandom=true”引數支援,但是隻有使用預設構造器的時候,才會使用SecureRandom輔助生成初始seed。即不指定初始seed時,同一個SplittableRandom例項多次執行,或者不同的例項執行,結果是不同的。
其中有一個split()方法,用來構造並返回與新的例項,這個例項共享了一些不可變狀態。需要注意,split產生的新SplittableRandom例項,與原例項並不存在內部資料的併發競爭,也不會交替或者延續原例項的隨機數生成序列(即兩個例項產出隨機序列的一致性,與原例項沒有關係,只是在統計值層面更加接近);但是程式碼一致性的情況下,多次執行,其隨機數序列的結果總是一致的(假如初始seed是指定的,而非預設),這一點與Random、ThreadLocalRandom表現相同。
public SplittableRandom split() {
return new SplittableRandom(nextLong(), mixGamma(nextSeed()));
}
樣例程式碼。
System.out.println("第一段");
SplittableRandom random = new SplittableRandom(100);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
SplittableRandom _random = random.split();
for (int i=0; i < 5; i++) {
System.out.println("---" + _random.nextInt(100));
}
}
});
thread.start();
thread.join();
for (int i=0; i < 5; i++) {
System.out.println("+++" + random.nextInt(100));
}
System.out.println("第二段");
SplittableRandom _random = new SplittableRandom(100);
for (int i=0; i < 10; i++) {
System.out.println("..." + _random.nextInt(100));
}
執行結果。
第一段
---71
---85
---10
---60
---98
+++44
+++87
+++77
+++67
+++72
第二段
...92
...30
...44
...87
...77
...67
...72
...23
...9
...64
從執行結果上看,split產生的random例項與原例項執行結果上沒有相似之處;但是不同SplittableRandom例項(無論是否執行過split),其產出隨機數序列是一致的。
效能檢測
簡析,基準:100000隨機數,單執行緒
1、 Random :2毫秒
2、 ThreadLocalRandom :1毫秒
3、 SecureRandom
1)預設演算法,即SHAR1PRNG:80毫秒左右。
2)NativePRNG:90毫秒左右。
4、 SplittableRandom :1毫秒
End
平常使用Random,或者大多數時候使用,都是沒有問題的,它也是執行緒安全的。SplittableRandom是內部使用的類,應用較少,即使它也是public的也掩飾不了偏門。ThreadLocalRandom是為了在高併發環境下節省一點細微的時間,追求效能的應用推薦使用。而對於有安全需求的,又希望更隨機
一些的,用SecureRandom再好不過了。
jdk竟然有這麼多隨機數生成器。有沒有大吃一精?我反正是跪了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/756/viewspace-2823271/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 指令式Callback,函式式Promise:對node.js的一聲嘆息函式PromiseNode.js
- 面試之後,扼腕嘆息。面試
- JDK安全證書一個錯誤訊息 No subject alternative names presentJDK
- 中國高校竟然有兩個“智慧”專業?
- 跨越資料的“嘆息牆”:華為下一代資料湖與HPDA時代
- 隨機不只是 Math.random —— 前端噪聲應用隨機random前端
- iPhone14:一個好訊息,一個壞訊息!果粉:習慣了“擠牙膏”iPhone
- 同一個電腦安裝兩個jdk版本JDK
- C++ 利用 標頭檔案 , 產生一個random number generatorC++random
- OpenJDK JDK 18第一個版本釋出JDK
- JDK下載與第一個java程式JDKJava
- 利用arc4random_uniform()產生隨機數randomORM隨機
- jdk 原始碼的一個BUG,大家來看看JDK原始碼
- 令人感嘆的10個非主流作業系統作業系統
- 如何設計一個訊息佇列?佇列
- random 模組random
- 銀行業務_小嘆行業
- 20個Python random模組的程式碼示例Pythonrandom
- Random和Math.random()簡單總結random
- 程式設計師嘆息:太難了,三萬的程式設計師確實不如三千公務員?程式設計師
- 多個Jdk版本(轉)JDK
- JDK8新特性(4)—— stream 流JDK
- 用 Go 寫一個簡單訊息佇列(一):定義訊息和基礎工具Go佇列
- 一個App賣了4億美元,這家聽聲識曲公司為何得到Apple的青睞?APP
- 總結:JDK1.5-JDK1.8各個新特性JDK
- Math.random()random
- Math.randomrandom
- random()函式random函式
- dbms_randomrandom
- [譯] 看!Swift 裡竟然有紅綠燈 ?!Swift
- 【乾貨】JDK動態代理的實現原理以及如何手寫一個JDK動態代理JDK
- 自己實現一個Electron跨程式訊息元件元件
- Protocol Buffers學習(1):定義一個訊息Protocol
- JDK的第三個LTS版本JDK17來了JDK
- 一個簡單API,一鍵實現多通道訊息推送API
- random隨機生成10個數,然後氣泡排序random隨機排序
- 寫一個函式,輸入一個4位數字,要求輸出這4個數字字元函式字元
- 【DBMS_RANDOM】使用 DBMS_RANDOM包生成隨機字串random隨機字串