掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
在 Java 中,生成隨機(jī)數(shù)的場景有很多,所以本文我們就來盤點(diǎn)一下 4 種生成隨機(jī)數(shù)的方式,以及它們之間的區(qū)別和每種生成方式所對應(yīng)的場景。

成都創(chuàng)新互聯(lián)專注于哈爾濱網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供哈爾濱營銷型網(wǎng)站建設(shè),哈爾濱網(wǎng)站制作、哈爾濱網(wǎng)頁設(shè)計、哈爾濱網(wǎng)站官網(wǎng)定制、小程序開發(fā)服務(wù),打造哈爾濱網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供哈爾濱網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
Random 類誕生于 JDK 1.0,它產(chǎn)生的隨機(jī)數(shù)是偽隨機(jī)數(shù),也就是有規(guī)則的隨機(jī)數(shù)。Random 使用的隨機(jī)算法為 linear congruential pseudorandom number generator (LGC) 線性同余法偽隨機(jī)數(shù)。在隨機(jī)數(shù)生成時,隨機(jī)算法的起源數(shù)字稱為種子數(shù)(seed),在種子數(shù)的基礎(chǔ)上進(jìn)行一定的變換,從而產(chǎn)生需要的隨機(jī)數(shù)字。
Random 對象在種子數(shù)相同的情況下,相同次數(shù)生成的隨機(jī)數(shù)是相同的。比如兩個種子數(shù)相同的 Random 對象,第一次生成的隨機(jī)數(shù)字完全相同,第二次生成的隨機(jī)數(shù)字也完全相同。默認(rèn)情況下 new Random() 使用的是當(dāng)前納秒時間作為種子數(shù)的。
使用 Random 生成一個從 0 到 10 的隨機(jī)數(shù)(不包含 10),實現(xiàn)代碼如下:
- // 生成 Random 對象
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- // 生成 0-9 隨機(jī)整數(shù)
- int number = random.nextInt(10);
- System.out.println("生成隨機(jī)數(shù):" + number);
- }
以上程序的執(zhí)行結(jié)果為:
Random 使用 LGC 算法生成偽隨機(jī)數(shù)的優(yōu)點(diǎn)是執(zhí)行效率比較高,生成的速度比較快。
它的缺點(diǎn)是如果 Random 的隨機(jī)種子一樣的話,每次生成的隨機(jī)數(shù)都是可預(yù)測的(都是一樣的)。如下代碼所示,當(dāng)我們給兩個線程設(shè)置相同的種子數(shù)的時候,會發(fā)現(xiàn)每次產(chǎn)生的隨機(jī)數(shù)也是相同的:
- // 創(chuàng)建兩個線程
- or (int i = 0; i < 2; i++) {
- new Thread(() -> {
- // 創(chuàng)建 Random 對象,設(shè)置相同的種子
- Random random = new Random(1024);
- // 生成 3 次隨機(jī)數(shù)
- for (int j = 0; j < 3; j++) {
- // 生成隨機(jī)數(shù)
- int number = random.nextInt();
- // 打印生成的隨機(jī)數(shù)
- System.out.println(Thread.currentThread().getName() + ":" +
- number);
- // 休眠 200 ms
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("---------------------");
- }
- }).start();
以上程序的執(zhí)行結(jié)果為:
當(dāng)我們要使用一個類時,我們首先關(guān)心的第一個問題是:它是否為線程安全?對于 Random 來說,Random 是線程安全的。
我們來看 Random 的實現(xiàn)源碼:
- public Random() {
- this(seedUniquifier() ^ System.nanoTime());
- }
- 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)); // CAS(Compare and Swap)生成隨機(jī)數(shù)
- return (int)(nextseed >>> (48 - bits));
- }
從以上源碼可以看出,Random 底層使用的是 CAS(Compare and Swap,比較并替換)來解決線程安全問題的,因此對于絕大數(shù)隨機(jī)數(shù)生成的場景,使用 Random 不乏為一種很好的選擇。
CAS 是 Compare And Swap(比較并替換)的縮寫,java.util.concurrent.atomic 中的很多類,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了 CAS 機(jī)制來實現(xiàn)。
ThreadLocalRandom 是 JDK 1.7 新提供的類,它屬于 JUC(java.util.concurrent)下的一員,為什么有了 Random 之后還會再創(chuàng)建一個 ThreadLocalRandom?
原因很簡單,通過上面 Random 的源碼我們可以看出,Random 在生成隨機(jī)數(shù)時使用的 CAS 來解決線程安全問題的,然而 CAS 在線程競爭比較激烈的場景中效率是非常低的,原因是 CAS 對比時老有其他的線程在修改原來的值,所以導(dǎo)致 CAS 對比失敗,所以它要一直循環(huán)來嘗試進(jìn)行 CAS 操作。所以在多線程競爭比較激烈的場景可以使用 ThreadLocalRandom 來解決 Random 執(zhí)行效率比較低的問題。
當(dāng)我們第一眼看到 ThreadLocalRandom 的時候,一定會聯(lián)想到一次類 ThreadLocal,確實如此。ThreadLocalRandom 的實現(xiàn)原理與 ThreadLocal 類似,它相當(dāng)于給每個線程一個自己的本地種子,從而就可以避免因多個線程競爭一個種子,而帶來的額外性能開銷了。
接下來我們使用 ThreadLocalRandom 來生成一個 0 到 10 的隨機(jī)數(shù)(不包含 10),實現(xiàn)代碼如下:
- // 得到 ThreadLocalRandom 對象
- ThreadLocalRandom random = ThreadLocalRandom.current();
- for (int i = 0; i < 10; i++) {
- // 生成 0-9 隨機(jī)整數(shù)
- int number = random.nextInt(10);
- // 打印結(jié)果
- System.out.println("生成隨機(jī)數(shù):" + number);
- }
以上程序的執(zhí)行結(jié)果為:
ThreadLocalRandom 的實現(xiàn)原理和 ThreadLocal 類似,它是讓每個線程持有自己的本地種子,該種子在生成隨機(jī)數(shù)時候才會被初始化,實現(xiàn)源碼如下:
- public int nextInt(int bound) {
- // 參數(shù)效驗
- if (bound <= 0)
- throw new IllegalArgumentException(BadBound);
- // 根據(jù)當(dāng)前線程中種子計算新種子
- int r = mix32(nextSeed());
- int m = bound - 1;
- // 根據(jù)新種子和 bound 計算隨機(jī)數(shù)
- if ((bound & m) == 0) // power of two
- r &= m;
- else { // reject over-represented candidates
- for (int u = r >>> 1;
- u + m - (r = u % bound) < 0;
- u = mix32(nextSeed()) >>> 1)
- ;
- }
- return r;
- }
- final long nextSeed() {
- Thread t; long r; // read and update per-thread seed
- // 獲取當(dāng)前線程中 threadLocalRandomSeed 變量,然后在種子的基礎(chǔ)上累加 GAMMA 值作為新種子
- // 再使用 UNSAFE.putLong 將新種子存放到當(dāng)前線程的 threadLocalRandomSeed 變量中
- UNSAFE.putLong(t = Thread.currentThread(), SEED,
- r = UNSAFE.getLong(t, SEED) + GAMMA);
- return r;
- }
ThreadLocalRandom 結(jié)合了 Random 和 ThreadLocal 類,并被隔離在當(dāng)前線程中。因此它通過避免競爭操作種子數(shù),從而在多線程運(yùn)行的環(huán)境中實現(xiàn)了更好的性能,而且也保證了它的線程安全。
另外,不同于 Random, ThreadLocalRandom 明確不支持設(shè)置隨機(jī)種子。它重寫了 Random 的setSeed(long seed) 方法并直接拋出了 UnsupportedOperationException異常,因此降低了多個線程出現(xiàn)隨機(jī)數(shù)重復(fù)的可能性。
源碼如下:
- public void setSeed(long seed) {
- // only allow call from super() constructor
- if (initialized)
- throw new UnsupportedOperationException();
- }
只要程序中調(diào)用了 setSeed() 方法就會拋出 UnsupportedOperationException 異常,如下圖所示:
雖然 ThreadLocalRandom 不支持手動設(shè)置隨機(jī)種子的方法,但并不代表 ThreadLocalRandom 就是完美的,當(dāng)我們查看 ThreadLocalRandom 初始化隨機(jī)種子的方法 initialSeed() 源碼時發(fā)現(xiàn),默認(rèn)情況下它的隨機(jī)種子也是以當(dāng)前時間有關(guān),源碼如下:
- private static long initialSeed() {
- // 嘗試獲取 JVM 的啟動參數(shù)
- String sec = VM.getSavedProperty("java.util.secureRandomSeed");
- // 如果啟動參數(shù)設(shè)置的值為 true,則參數(shù)一個隨機(jī) 8 位的種子
- 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;
- }
- // 如果沒有設(shè)置啟動參數(shù),則使用當(dāng)前時間有關(guān)的隨機(jī)種子算法
- return (mix64(System.currentTimeMillis()) ^
- mix64(System.nanoTime()));
- }
從上述源碼可以看出,當(dāng)我們設(shè)置了啟動參數(shù)“-Djava.util.secureRandomSeed=true”時,ThreadLocalRandom 會產(chǎn)生一個隨機(jī)種子,一定程度上能緩解隨機(jī)種子相同所帶來隨機(jī)數(shù)可預(yù)測的問題,然而默認(rèn)情況下如果不設(shè)置此參數(shù),那么在多線程中就可以因為啟動時間相同,而導(dǎo)致多個線程在每一步操作中都會生成相同的隨機(jī)數(shù)。
SecureRandom 繼承自 Random,該類提供加密強(qiáng)隨機(jī)數(shù)生成器。SecureRandom 不同于 Random,它收集了一些隨機(jī)事件,比如鼠標(biāo)點(diǎn)擊,鍵盤點(diǎn)擊等,SecureRandom 使用這些隨機(jī)事件作為種子。這意味著,種子是不可預(yù)測的,而不像 Random 默認(rèn)使用系統(tǒng)當(dāng)前時間的毫秒數(shù)作為種子,從而避免了生成相同隨機(jī)數(shù)的可能性。
- // 創(chuàng)建 SecureRandom 對象,并設(shè)置加密算法
- SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
- for (int i = 0; i < 10; i++) {
- // 生成 0-9 隨機(jī)整數(shù)
- int number = random.nextInt(10);
- // 打印結(jié)果
- System.out.println("生成隨機(jī)數(shù):" + number);
- }
以上程序的執(zhí)行結(jié)果為:
SecureRandom 默認(rèn)支持兩種加密算法:
當(dāng)然除了上述的操作方式之外,你還可以選擇使用 new SecureRandom() 來創(chuàng)建 SecureRandom 對象,實現(xiàn)代碼如下:
- SecureRandom secureRandom = new SecureRandom();
通過 new 初始化 SecureRandom,默認(rèn)會使用 NativePRNG 算法來生成隨機(jī)數(shù),但是也可以配置 JVM 啟動參數(shù)“-Djava.security”參數(shù)來修改生成隨機(jī)數(shù)的算法,或選擇使用 getInstance("算法名稱") 的方式來指定生成隨機(jī)數(shù)的算法。
Math 類誕生于 JDK 1.0,它里面包含了用于執(zhí)行基本數(shù)學(xué)運(yùn)算的屬性和方法,如初等指數(shù)、對數(shù)、平方根和三角函數(shù),當(dāng)然它里面也包含了生成隨機(jī)數(shù)的靜態(tài)方法 Math.random() ,此方法會產(chǎn)生一個 0 到 1 的 double 值,如下代碼所示。
- for (int i = 0; i < 10; i++) {
- // 產(chǎn)生隨機(jī)數(shù)
- double number = Math.random();
- System.out.println("生成隨機(jī)數(shù):" + number);
- }
以上程序的執(zhí)行結(jié)果為:
當(dāng)然如果你想用它來生成一個一定范圍的 int 值也是可以的,你可以這樣寫:
- for (int i = 0; i < 10; i++) {
- // 生成一個從 0-99 的整數(shù)
- int number = (int) (Math.random() * 100);
- System.out.println("生成隨機(jī)數(shù):" + number);
- }
以上程序的執(zhí)行結(jié)果為:
通過分析 Math 的源碼我們可以得知:當(dāng)?shù)谝淮握{(diào)用 Math.random() 方法時,自動創(chuàng)建了一個偽隨機(jī)數(shù)生成器,實際上用的是 new java.util.Random(),當(dāng)下一次繼續(xù)調(diào)用 Math.random() 方法時,就會使用這個新的偽隨機(jī)數(shù)生成器。
源碼如下:
- public static double random() {
- return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
- }
- private static final class RandomNumberGeneratorHolder {
- static final Random randomNumberGenerator = new Random();
- }
本文我們介紹了 4 種生成隨機(jī)數(shù)的方法,其中 Math 是對 Random 的封裝,所以二者比較類似。Random 生成的是偽隨機(jī)數(shù),是以當(dāng)前納秒時間作為種子數(shù)的,并且在多線程競爭比較激烈的情況下因為要進(jìn)行 CAS 操作,所以存在一定的性能問題,但對于絕大數(shù)應(yīng)用場景來說,使用 Random 已經(jīng)足夠了。當(dāng)在競爭比較激烈的場景下可以使用 ThreadLocalRandom 來替代 Random,但如果對安全性要求比較高的情況下,可以使用 SecureRandom 來生成隨機(jī)數(shù),因為 SecureRandom 會收集一些隨機(jī)事件來作為隨機(jī)種子,所以 SecureRandom 可以看作是生成真正隨機(jī)數(shù)的一個工具類。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流