你知道jdk竟有4個random嗎
我們從jdk8說起。主要是四個隨機數(shù)生成器。神馬?有四個?
接下來我們簡單說下這幾個類的使用場景,來了解其中的細微差別,和api設(shè)計者的良苦用心。
java.util.Randomjava.util.concurrent.ThreadLocalRandomjava.security.SecureRandomjava.util.SplittableRandom
Random
最常用的就是Random。
用來生成偽隨機數(shù),默認使用48位種子、線性同余公式進行修改。我們可以通過構(gòu)造器傳入初始seed,或者通過setSeed重置(同步)。默認seed為系統(tǒng)時間的納秒數(shù),真大!
如果兩個(多個)不同的Random實例,使用相同的seed,按照相同的順序調(diào)用相同方法,那么它們得到的數(shù)字序列也是相同的。這看起來不太隨機。 這種設(shè)計策略,既有優(yōu)點也有缺點,優(yōu)點是“相同seed”生成的序列是一致的,使過程具有可回溯和校驗性(平臺無關(guān)、運行時機無關(guān));缺點就是,這種一致性,潛在引入其“可被預(yù)測”的風險。
Random的實例是線程安全的。 但是,跨線程并發(fā)使用相同的java.util.Random實例可能會遇到爭用,從而導(dǎo)致性能稍欠佳(nextX方法中,在對seed賦值時使用了CAS,測試結(jié)果顯示,其實性能損耗很小)。請考慮在多線程設(shè)計中使用ThreadLocalRandom。同時,我們在并發(fā)環(huán)境下,也沒有必要刻意使用多個Random實例。
Random實例不具有加密安全性。 相反,請考慮使用SecureRandom來獲取加密安全的偽隨機數(shù)生成器,以供安全敏感應(yīng)用程序使用。
Random是最常用的隨機數(shù)生成類,適用于絕大部分場景。
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。調(diào)用過程一樣,其中產(chǎn)生的隨機數(shù)序列也是完全一樣的。多次執(zhí)行結(jié)果也完全一致,簡單而言,只要初始seed一樣,即使實例不同,多次運行它們的結(jié)果都是一致的。這個現(xiàn)象與上面所說的一致。
如果Random構(gòu)造器中不指定seed,而是使用默認的系統(tǒng)時間納秒數(shù)作為主導(dǎo)變量,三個random實例執(zhí)行的結(jié)果是不同的。多次執(zhí)行結(jié)果也不一樣。由此可見,seed是否具有隨機性,在一定程度上,也決定了Random產(chǎn)生結(jié)果的隨機性。
所以,在分布式或者多線程環(huán)境下,如果Random實例處于代碼一致的tasks線程中,可能這些分布式進程或者線程,產(chǎn)出的序列值是一樣的。這也是在JDK 7引入ForkJoin的同時,也引入了ThreadLocalRandom類。
ThreadLocalRandom
這個類的作用,使得隨機數(shù)的生成器隔離到當前線程。此類繼承自java.util.Random,與Math類使用的全局Random生成器一樣,ThreadLocalRandom使用內(nèi)部生成的種子進行初始化,否則可能無法修改。
在并發(fā)程序中使用ThreadLocalRandom,通常會有更少的開銷和競爭。當多個任務(wù)(例如,每個ForkJoinTask)在線程池中并行使用隨機數(shù)時,ThreadLocalRandom是特別合適的。
切記,在多個線程中不應(yīng)該共享ThreadLocalRandom實例。
ThreadLocalRandom初始化是private的,所以無法通過構(gòu)造器設(shè)定seed,此外其setSeed方法也被重寫而不支持(拋出異常)。默認情況下,每個ThreadLocalRandom實例的seed主導(dǎo)變量值為系統(tǒng)時間(納秒):
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()));}
根據(jù)其初始化seed的實現(xiàn),我們也可以通過JVM啟動參數(shù)增加“-Djava.util.secureRandomSeed=true”,此時初始seed變量將不再是系統(tǒng)時間,而是由SecureRandom類生成一個隨機因子,以此作為ThreadLoalRandom的初始seed。
真是夠繞的。
從源碼中,我并沒有看到Thread-ID作為變量生成seed,而且nextX方法中隨機數(shù)生成算法也具有一致性。這意味著,如果多個線程初始ThreadLocalRandom的時間完全一致,在調(diào)用方法和過程相同的情況下,產(chǎn)生的隨機序列也是相同的;在一定程度上“-Djava.util.secureRandom=true”可以規(guī)避此問題。
ThreadLocalRandom并沒有使用ThreadLocal來支持內(nèi)部數(shù)據(jù)存儲等,而是直接使用UnSafe操作當前Thread對象引用中seed屬性的內(nèi)存地址并進行數(shù)據(jù)操作,我比較佩服SUN的這種巧妙的做法。
SecureRandom
它也繼承自Random,該類提供加密強隨機數(shù)生成器(RNG),加密強隨機數(shù)最低限度符合FIPS 140-2“加密模塊的安全要求”。此外,SecureRandom必須產(chǎn)生非確定性輸出。因此,傳遞給SecureRandom對象的任何種子材料必須是不可預(yù)測的,并且所有SecureRandom輸出序列必須具有加密強度。(官文,其實我也一知半解)
SecureRandom默認支持兩種RNG加密算法實現(xiàn):
”SHA1PRNG”算法提供者sun.security.provider.SecureRandom
”NativePRNG”提供者sun.security.provider.NativePRNG
默認情況下,是“SHA1PRNG”,即SUN提供的實現(xiàn)。此外可以通過
“-Djava.security=file:/dev/urandom”
(推薦)或者
“-Djava.security=file:/dev/random”
指定使用linux本地的隨機算法,
即NativePRNG;
其中“/dev/random”與“/dev/urandom”在不同unix-*平臺中實現(xiàn)有所不同,性能也有所差異,建議使用“/dev/urandom”。
/dev/random的一個副本是/dev/urandom (”unlocked”,非阻塞的隨機數(shù)發(fā)生器),它會重復(fù)使用熵池中的數(shù)據(jù)以產(chǎn)生偽隨機數(shù)據(jù)。這表示對/dev/urandom的讀取操作不會產(chǎn)生阻塞,但其輸出的熵可能小于/dev/random的。它可以作為生成較低強度密碼的偽隨機數(shù)生成器,不建議用于生成高強度長期密碼。
算法的內(nèi)部實現(xiàn),比較復(fù)雜;本人測試,其實性能差不不太大(JDK 8環(huán)境)。SecureRandom也是線程安全的。
從輸出結(jié)果上分析,無論是否指定SecureRandom的初始seed,單個實例多次運行的結(jié)果也完全不同 ;多個不同的SecureRandom實例無論是否指定seed,即使指定一樣的初始seed,同時運行的結(jié)果也完全不同。
SecureRandom繼承自Random,但是對nextX方法中的底層方法進行的重寫覆蓋,不過仍然基于Random的CAS且SecureRandom的底層方法還使用的同步,所以在并發(fā)環(huán)境下,性能比Random差了一些。
SplittableRandom
JDK 8 新增的API,主要適用于Fork/join形式的跨線程操作中。它并沒有繼承java.util.Random類。
具有相同seed的不同SplittableRandom實例或者同一個SplittableRandom,多次運行結(jié)果是一致的。這和Random是一致的。
非線程安全,不能被并發(fā)使用。 (不會報錯,但是并發(fā)時可能多個線程同時得到相同的隨機數(shù))
同ThreadLocalRandom,對“-Djava.util.secureRandom=true”參數(shù)支持,但是只有使用默認構(gòu)造器的時候,才會使用SecureRandom輔助生成初始seed。即不指定初始seed時,同一個SplittableRandom實例多次運行,或者不同的實例運行,結(jié)果是不同的。
其中有一個split()方法,用來構(gòu)造并返回與新的實例,這個實例共享了一些不可變狀態(tài)。需要注意,split產(chǎn)生的新SplittableRandom實例,與原實例并不存在內(nèi)部數(shù)據(jù)的并發(fā)競爭,也不會交替或者延續(xù)原實例的隨機數(shù)生成序列(即兩個實例產(chǎn)出隨機序列的一致性,與原實例沒有關(guān)系,只是在統(tǒng)計值層面更加接近);但是代碼一致性的情況下,多次運行,其隨機數(shù)序列的結(jié)果總是一致的(假如初始seed是指定的,而非默認),這一點與Random、ThreadLocalRandom表現(xiàn)相同。
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));}
執(zhí)行結(jié)果。
第一段---71---85---10---60---98+++44+++87+++77+++67+++72第二段...92...30...44...87...77...67...72...23...9...64
從執(zhí)行結(jié)果上看,split產(chǎn)生的random實例與原實例執(zhí)行結(jié)果上沒有相似之處;但是不同SplittableRandom實例(無論是否執(zhí)行過split),其產(chǎn)出隨機數(shù)序列是一致的。
性能檢測
簡析,基準:100000隨機數(shù),單線程
1、 Random :2毫秒
2、 ThreadLocalRandom :1毫秒
3、 SecureRandom
1)默認算法,即SHAR1PRNG:80毫秒左右。
2)NativePRNG:90毫秒左右。
4、 SplittableRandom :1毫秒
End
平常使用Random,或者大多數(shù)時候使用,都是沒有問題的,它也是線程安全的。SplittableRandom是內(nèi)部使用的類,應(yīng)用較少,即使它也是public的也掩飾不了偏門。ThreadLocalRandom是為了在高并發(fā)環(huán)境下節(jié)省一點細微的時間,追求性能的應(yīng)用推薦使用。而對于有安全需求的,又希望更隨機一些的,用SecureRandom再好不過了。
jdk竟然有這么多隨機數(shù)生成器。有沒有大吃一精?我反正是跪了。
到此這篇關(guān)于jdk竟有4個random的文章就介紹到這了,更多相關(guān)jdk有4個random內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java線程池運行狀態(tài)監(jiān)控實現(xiàn)解析
這篇文章主要介紹了Java線程池運行狀態(tài)監(jiān)控實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10解決Java調(diào)用BAT批處理不彈出cmd窗口的方法分析
本篇文章是對Java調(diào)用BAT批處理不彈出cmd窗口的方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05Java中Queue的poll()和remove()區(qū)別詳解
這篇文章主要介紹了Java中Queue的poll()和remove()區(qū)別詳解,Queue接口提供了許多方法,其中poll()和remove()是兩個常用的方法,它們的區(qū)別在于,當隊列為空時,poll()方法返回null,而remove()方法會拋出,需要的朋友可以參考下2023-07-07