自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java生成隨機數(shù)的4種方式,以后就用它了!

開發(fā) 后端
本文我們介紹 4 種生成隨機數(shù)的方法,其中 Math 是對 Random 的封裝,所以二者比較類似。Random 生成的是偽隨機數(shù),是以當(dāng)前納秒時間作為種子數(shù)的,并且在多線程競爭比較激烈的情況下因為要進行 CAS 操作,所以存在一定的性能問題,但對于絕大數(shù)應(yīng)用場景來說,使用 Random 已經(jīng)足夠了。

[[405510]]

在 Java 中,生成隨機數(shù)的場景有很多,所以本文我們就來盤點一下 4 種生成隨機數(shù)的方式,以及它們之間的區(qū)別和每種生成方式所對應(yīng)的場景。

1.Random

Random 類誕生于 JDK 1.0,它產(chǎn)生的隨機數(shù)是偽隨機數(shù),也就是有規(guī)則的隨機數(shù)。Random 使用的隨機算法為 linear congruential pseudorandom number generator (LGC) 線性同余法偽隨機數(shù)。在隨機數(shù)生成時,隨機算法的起源數(shù)字稱為種子數(shù)(seed),在種子數(shù)的基礎(chǔ)上進行一定的變換,從而產(chǎn)生需要的隨機數(shù)字。

Random 對象在種子數(shù)相同的情況下,相同次數(shù)生成的隨機數(shù)是相同的。比如兩個種子數(shù)相同的 Random 對象,第一次生成的隨機數(shù)字完全相同,第二次生成的隨機數(shù)字也完全相同。默認(rèn)情況下 new Random() 使用的是當(dāng)前納秒時間作為種子數(shù)的。

① 基礎(chǔ)使用

使用 Random 生成一個從 0 到 10 的隨機數(shù)(不包含 10),實現(xiàn)代碼如下:

  1. // 生成 Random 對象 
  2. Random random = new Random(); 
  3. for (int i = 0; i < 10; i++) { 
  4.     // 生成 0-9 隨機整數(shù) 
  5.     int number = random.nextInt(10); 
  6.     System.out.println("生成隨機數(shù):" + number); 

以上程序的執(zhí)行結(jié)果為:

② 優(yōu)缺點分析

Random 使用 LGC 算法生成偽隨機數(shù)的優(yōu)點是執(zhí)行效率比較高,生成的速度比較快。

它的缺點是如果 Random 的隨機種子一樣的話,每次生成的隨機數(shù)都是可預(yù)測的(都是一樣的)。如下代碼所示,當(dāng)我們給兩個線程設(shè)置相同的種子數(shù)的時候,會發(fā)現(xiàn)每次產(chǎn)生的隨機數(shù)也是相同的:

  1. // 創(chuàng)建兩個線程 
  2. or (int i = 0; i < 2; i++) { 
  3.    new Thread(() -> { 
  4.        // 創(chuàng)建 Random 對象,設(shè)置相同的種子 
  5.        Random random = new Random(1024); 
  6.        // 生成 3 次隨機數(shù) 
  7.        for (int j = 0; j < 3; j++) { 
  8.            // 生成隨機數(shù) 
  9.            int number = random.nextInt(); 
  10.            // 打印生成的隨機數(shù) 
  11.            System.out.println(Thread.currentThread().getName() + ":" + 
  12.                               number); 
  13.            // 休眠 200 ms 
  14.            try { 
  15.                Thread.sleep(200); 
  16.            } catch (InterruptedException e) { 
  17.                e.printStackTrace(); 
  18.            } 
  19.            System.out.println("---------------------"); 
  20.        } 
  21.    }).start(); 

以上程序的執(zhí)行結(jié)果為:

③ 線程安全問題

當(dāng)我們要使用一個類時,我們首先關(guān)心的第一個問題是:它是否為線程安全?對于 Random 來說,Random 是線程安全的。

  • PS:線程安全指的是在多線程的場景下,程序的執(zhí)行結(jié)果和預(yù)期的結(jié)果一致,就叫線程安全的,否則則為非線程安全的(也叫線程安全問題)。比如有兩個線程,第一個線程執(zhí)行 10 萬次 ++ 操作,第二個線程執(zhí)行 10 萬次 -- 操作,那么最終的結(jié)果應(yīng)該是沒加也沒減,如果程序最終的結(jié)果和預(yù)期不符,則為非線程安全的。

我們來看 Random 的實現(xiàn)源碼:

  1. public Random() { 
  2.     this(seedUniquifier() ^ System.nanoTime()); 
  3.  
  4. public int nextInt() { 
  5.     return next(32); 
  6.  
  7. protected int next(int bits) { 
  8.     long oldseed, nextseed; 
  9.     AtomicLong seed = this.seed; 
  10.     do { 
  11.         oldseed = seed.get(); 
  12.         nextseed = (oldseed * multiplier + addend) & mask; 
  13.     } while (!seed.compareAndSet(oldseed, nextseed)); // CAS(Compare and Swap)生成隨機數(shù) 
  14.     return (int)(nextseed >>> (48 - bits)); 
  • PS:本文所有源碼來自于 JDK 1.8.0_211。

從以上源碼可以看出,Random 底層使用的是 CAS(Compare and Swap,比較并替換)來解決線程安全問題的,因此對于絕大數(shù)隨機數(shù)生成的場景,使用 Random 不乏為一種很好的選擇。

  • PS:Java 并發(fā)機制實現(xiàn)原子操作有兩種:一種是鎖,一種是 CAS。

CAS 是 Compare And Swap(比較并替換)的縮寫,java.util.concurrent.atomic 中的很多類,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了 CAS 機制來實現(xiàn)。

2.ThreadLocalRandom

ThreadLocalRandom 是 JDK 1.7 新提供的類,它屬于 JUC(java.util.concurrent)下的一員,為什么有了 Random 之后還會再創(chuàng)建一個 ThreadLocalRandom?

原因很簡單,通過上面 Random 的源碼我們可以看出,Random 在生成隨機數(shù)時使用的 CAS 來解決線程安全問題的,然而 CAS 在線程競爭比較激烈的場景中效率是非常低的,原因是 CAS 對比時老有其他的線程在修改原來的值,所以導(dǎo)致 CAS 對比失敗,所以它要一直循環(huán)來嘗試進行 CAS 操作。所以在多線程競爭比較激烈的場景可以使用 ThreadLocalRandom 來解決 Random 執(zhí)行效率比較低的問題。

當(dāng)我們第一眼看到 ThreadLocalRandom 的時候,一定會聯(lián)想到一次類 ThreadLocal,確實如此。ThreadLocalRandom 的實現(xiàn)原理與 ThreadLocal 類似,它相當(dāng)于給每個線程一個自己的本地種子,從而就可以避免因多個線程競爭一個種子,而帶來的額外性能開銷了。

① 基礎(chǔ)使用

接下來我們使用 ThreadLocalRandom 來生成一個 0 到 10 的隨機數(shù)(不包含 10),實現(xiàn)代碼如下:

  1. // 得到 ThreadLocalRandom 對象 
  2. ThreadLocalRandom random = ThreadLocalRandom.current(); 
  3. for (int i = 0; i < 10; i++) { 
  4.     // 生成 0-9 隨機整數(shù) 
  5.     int number = random.nextInt(10); 
  6.     // 打印結(jié)果 
  7.     System.out.println("生成隨機數(shù):" + number); 

以上程序的執(zhí)行結(jié)果為:

② 實現(xiàn)原理

ThreadLocalRandom 的實現(xiàn)原理和 ThreadLocal 類似,它是讓每個線程持有自己的本地種子,該種子在生成隨機數(shù)時候才會被初始化,實現(xiàn)源碼如下:

  1. public int nextInt(int bound) { 
  2.     // 參數(shù)效驗 
  3.     if (bound <= 0) 
  4.         throw new IllegalArgumentException(BadBound); 
  5.     // 根據(jù)當(dāng)前線程中種子計算新種子 
  6.     int r = mix32(nextSeed()); 
  7.     int m = bound - 1; 
  8.     // 根據(jù)新種子和 bound 計算隨機數(shù) 
  9.     if ((bound & m) == 0) // power of two 
  10.         r &= m; 
  11.     else { // reject over-represented candidates 
  12.         for (int u = r >>> 1; 
  13.              u + m - (r = u % bound) < 0; 
  14.              u = mix32(nextSeed()) >>> 1) 
  15.             ; 
  16.     } 
  17.     return r; 
  18.  
  19. final long nextSeed() { 
  20.     Thread t; long r; // read and update per-thread seed 
  21.     // 獲取當(dāng)前線程中 threadLocalRandomSeed 變量,然后在種子的基礎(chǔ)上累加 GAMMA 值作為新種子 
  22.     // 再使用 UNSAFE.putLong 將新種子存放到當(dāng)前線程的 threadLocalRandomSeed 變量中 
  23.     UNSAFE.putLong(t = Thread.currentThread(), SEED, 
  24.                    r = UNSAFE.getLong(t, SEED) + GAMMA);  
  25.     return r; 

③ 優(yōu)缺點分析

ThreadLocalRandom 結(jié)合了 Random 和 ThreadLocal 類,并被隔離在當(dāng)前線程中。因此它通過避免競爭操作種子數(shù),從而在多線程運行的環(huán)境中實現(xiàn)了更好的性能,而且也保證了它的線程安全。

另外,不同于 Random, ThreadLocalRandom 明確不支持設(shè)置隨機種子。它重寫了 Random 的setSeed(long seed) 方法并直接拋出了 UnsupportedOperationException異常,因此降低了多個線程出現(xiàn)隨機數(shù)重復(fù)的可能性。

源碼如下:

  1. public void setSeed(long seed) { 
  2.     // only allow call from super() constructor 
  3.     if (initialized) 
  4.         throw new UnsupportedOperationException(); 

只要程序中調(diào)用了 setSeed() 方法就會拋出 UnsupportedOperationException 異常,如下圖所示:

ThreadLocalRandom 缺點分析

雖然 ThreadLocalRandom 不支持手動設(shè)置隨機種子的方法,但并不代表 ThreadLocalRandom 就是完美的,當(dāng)我們查看 ThreadLocalRandom 初始化隨機種子的方法 initialSeed() 源碼時發(fā)現(xiàn),默認(rèn)情況下它的隨機種子也是以當(dāng)前時間有關(guān),源碼如下:

  1. private static long initialSeed() { 
  2.     // 嘗試獲取 JVM 的啟動參數(shù) 
  3.     String sec = VM.getSavedProperty("java.util.secureRandomSeed"); 
  4.     // 如果啟動參數(shù)設(shè)置的值為 true,則參數(shù)一個隨機 8 位的種子 
  5.     if (Boolean.parseBoolean(sec)) { 
  6.         byte[] seedBytes = java.security.SecureRandom.getSeed(8); 
  7.         long s = (long)(seedBytes[0]) & 0xffL; 
  8.         for (int i = 1; i < 8; ++i) 
  9.             s = (s << 8) | ((long)(seedBytes[i]) & 0xffL); 
  10.         return s; 
  11.     } 
  12.     // 如果沒有設(shè)置啟動參數(shù),則使用當(dāng)前時間有關(guān)的隨機種子算法 
  13.     return (mix64(System.currentTimeMillis()) ^ 
  14.             mix64(System.nanoTime())); 

從上述源碼可以看出,當(dāng)我們設(shè)置了啟動參數(shù)“-Djava.util.secureRandomSeed=true”時,ThreadLocalRandom 會產(chǎn)生一個隨機種子,一定程度上能緩解隨機種子相同所帶來隨機數(shù)可預(yù)測的問題,然而默認(rèn)情況下如果不設(shè)置此參數(shù),那么在多線程中就可以因為啟動時間相同,而導(dǎo)致多個線程在每一步操作中都會生成相同的隨機數(shù)。

3.SecureRandom

SecureRandom 繼承自 Random,該類提供加密強隨機數(shù)生成器。SecureRandom 不同于 Random,它收集了一些隨機事件,比如鼠標(biāo)點擊,鍵盤點擊等,SecureRandom 使用這些隨機事件作為種子。這意味著,種子是不可預(yù)測的,而不像 Random 默認(rèn)使用系統(tǒng)當(dāng)前時間的毫秒數(shù)作為種子,從而避免了生成相同隨機數(shù)的可能性。

基礎(chǔ)使用

  1. // 創(chuàng)建 SecureRandom 對象,并設(shè)置加密算法 
  2. SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 
  3. for (int i = 0; i < 10; i++) { 
  4.     // 生成 0-9 隨機整數(shù) 
  5.     int number = random.nextInt(10); 
  6.     // 打印結(jié)果 
  7.     System.out.println("生成隨機數(shù):" + number); 

以上程序的執(zhí)行結(jié)果為:

SecureRandom 默認(rèn)支持兩種加密算法:

  • SHA1PRNG 算法,提供者 sun.security.provider.SecureRandom;
  • NativePRNG 算法,提供者 sun.security.provider.NativePRNG。

當(dāng)然除了上述的操作方式之外,你還可以選擇使用 new SecureRandom() 來創(chuàng)建 SecureRandom 對象,實現(xiàn)代碼如下:

  1. SecureRandom secureRandom = new SecureRandom(); 

通過 new 初始化 SecureRandom,默認(rèn)會使用 NativePRNG 算法來生成隨機數(shù),但是也可以配置 JVM 啟動參數(shù)“-Djava.security”參數(shù)來修改生成隨機數(shù)的算法,或選擇使用 getInstance("算法名稱") 的方式來指定生成隨機數(shù)的算法。

4.Math

Math 類誕生于 JDK 1.0,它里面包含了用于執(zhí)行基本數(shù)學(xué)運算的屬性和方法,如初等指數(shù)、對數(shù)、平方根和三角函數(shù),當(dāng)然它里面也包含了生成隨機數(shù)的靜態(tài)方法 Math.random() ,此方法會產(chǎn)生一個 0 到 1 的 double 值,如下代碼所示。

① 基礎(chǔ)使用

  1. for (int i = 0; i < 10; i++) { 
  2.     // 產(chǎn)生隨機數(shù) 
  3.     double number = Math.random(); 
  4.     System.out.println("生成隨機數(shù):" + number); 

以上程序的執(zhí)行結(jié)果為:

② 擴展

當(dāng)然如果你想用它來生成一個一定范圍的 int 值也是可以的,你可以這樣寫:

  1. for (int i = 0; i < 10; i++) { 
  2.     // 生成一個從 0-99 的整數(shù) 
  3.     int number = (int) (Math.random() * 100); 
  4.     System.out.println("生成隨機數(shù):" + number); 

以上程序的執(zhí)行結(jié)果為:

③ 實現(xiàn)原理

通過分析 Math 的源碼我們可以得知:當(dāng)?shù)谝淮握{(diào)用 Math.random() 方法時,自動創(chuàng)建了一個偽隨機數(shù)生成器,實際上用的是 new java.util.Random(),當(dāng)下一次繼續(xù)調(diào)用 Math.random() 方法時,就會使用這個新的偽隨機數(shù)生成器。

源碼如下:

  1. public static double random() { 
  2.     return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble(); 
  3.  
  4. private static final class RandomNumberGeneratorHolder { 
  5.     static final Random randomNumberGenerator = new Random(); 

 總結(jié)

本文我們介紹了 4 種生成隨機數(shù)的方法,其中 Math 是對 Random 的封裝,所以二者比較類似。Random 生成的是偽隨機數(shù),是以當(dāng)前納秒時間作為種子數(shù)的,并且在多線程競爭比較激烈的情況下因為要進行 CAS 操作,所以存在一定的性能問題,但對于絕大數(shù)應(yīng)用場景來說,使用 Random 已經(jīng)足夠了。當(dāng)在競爭比較激烈的場景下可以使用 ThreadLocalRandom 來替代 Random,但如果對安全性要求比較高的情況下,可以使用 SecureRandom 來生成隨機數(shù),因為 SecureRandom 會收集一些隨機事件來作為隨機種子,所以 SecureRandom 可以看作是生成真正隨機數(shù)的一個工具類。

 

責(zé)任編輯:姜華 來源: Java中文社群
相關(guān)推薦

2019-12-26 14:07:19

隨機數(shù)偽隨機多線程

2024-11-01 15:51:06

2019-09-11 10:09:00

Java虛擬機算法

2009-12-02 17:01:01

PHP隨機數(shù)rand()

2010-03-22 19:41:31

2012-03-22 09:31:14

Java

2017-05-29 09:56:25

2010-03-11 12:48:25

Python生成隨機數(shù)

2024-05-15 09:09:49

2022-12-15 08:54:28

JAVA性能JDK

2011-07-08 15:11:03

JAVA

2009-06-11 15:16:18

不重復(fù)隨機數(shù)Java

2015-10-13 10:00:58

Swift隨機數(shù)使用總結(jié)

2021-12-27 09:31:20

HashtableJava隨機數(shù)

2014-07-23 10:07:34

2025-01-17 00:00:00

Java隨機數(shù)服務(wù)

2023-01-03 07:49:45

Java隨機數(shù)線程

2021-06-01 22:31:57

區(qū)塊鏈隨機數(shù)技術(shù)

2024-01-25 11:32:21

2010-10-09 15:35:25

MySQL rand函
點贊
收藏

51CTO技術(shù)棧公眾號