Java生成隨機數的4種方式,以後就用它了!

2021-12-23 CSDN

作者 | Java中文社群    責編 | 夢依丹

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


Random

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

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

① 基礎使用

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

Random random = new Random();for (int i = 0; i < 10; i++) {        int number = random.nextInt(10);    System.out.println("生成隨機數:" + number);}

以上程序的執行結果為:

② 優缺點分析

Random 使用 LGC 算法生成偽隨機數的優點是執行效率比較高,生成的速度比較快。

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

for (int i = 0; i < 2; i++) {    new Thread(() -> {                Random random = new Random(1024);                for (int j = 0; j < 3; j++) {                        int number = random.nextInt();                        System.out.println(Thread.currentThread().getName() + ":" +                               number);                        try {                Thread.sleep(200);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("-");        }    }).start();}

以上程序的執行結果為:

③ 線程安全問題

當我們要使用一個類時,我們首先關心的第一個問題是:它是否為線程安全?對於 Random 來說,Random 是線程安全的。

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

我們來看 Random 的實現源碼:

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)); return (int)(nextseed >>> (48 - bits));}


PS:本文所有源碼來自於 JDK 1.8.0_211。

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

PS:Java 並發機制實現原子操作有兩種:一種是鎖,一種是 CAS。

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

ThreadLocalRandom


ThreadLocalRandom 是 JDK 1.7 新提供的類,它屬於 JUC(java.util.concurrent)下的一員,為什麼有了 Random 之後還會再創建一個 ThreadLocalRandom?

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

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

① 基礎使用

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

ThreadLocalRandom random = ThreadLocalRandom.current();for (int i = 0; i < 10; i++) {        int number = random.nextInt(10);        System.out.println("生成隨機數:" + number);}

以上程序的執行結果為:

② 實現原理

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

public int nextInt(int bound) {        if (bound <= 0)        thrownew IllegalArgumentException(BadBound);        int r = mix32(nextSeed());    int m = bound - 1;        if ((bound & m) == 0)         r &= m;    else {         for (int u = r >>> 1;             u + m - (r = u % bound) < 0;             u = mix32(nextSeed()) >>> 1)            ;    }    return r;}
final long nextSeed() { Thread t; long r; UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r;}

③ 優缺點分析

ThreadLocalRandom 結合了 Random 和 ThreadLocal 類,並被隔離在當前線程中。因此它通過避免競爭操作種子數,從而在多線程運行的環境中實現了更好的性能,而且也保證了它的線程安全。

另外,不同於 Random, ThreadLocalRandom 明確不支持設置隨機種子。它重寫了 Random 的setSeed(long seed) 方法並直接拋出了 UnsupportedOperationException 異常,因此降低了多個線程出現隨機數重複的可能性。

源碼如下:

public void setSeed(long seed) {        if (initialized)        thrownew UnsupportedOperationException();}

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

ThreadLocalRandom 缺點分析

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

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()));}

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

SecureRandom

SecureRandom 繼承自 Random,該類提供加密強隨機數生成器。SecureRandom 不同於 Random,它收集了一些隨機事件,比如滑鼠點擊,鍵盤點擊等,SecureRandom 使用這些隨機事件作為種子。這意味著,種子是不可預測的,而不像 Random 默認使用系統當前時間的毫秒數作為種子,從而避免了生成相同隨機數的可能性。

基礎使用
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");for (int i = 0; i < 10; i++) {        int number = random.nextInt(10);        System.out.println("生成隨機數:" + number);}

以上程序的執行結果為:

SecureRandom 默認支持兩種加密算法:

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

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

SecureRandom secureRandom = new SecureRandom();

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


Math


Math 類誕生於 JDK 1.0,它裡面包含了用於執行基本數學運算的屬性和方法,如初等指數、對數、平方根和三角函數,當然它裡面也包含了生成隨機數的靜態方法 Math.random() ,此方法會產生一個 0 到 1 的 double 值,如下代碼所示。

① 基礎使用
for (int i = 0; i < 10; i++) {        double number = Math.random();    System.out.println("生成隨機數:" + number);}

以上程序的執行結果為:

② 擴展

當然如果你想用它來生成一個一定範圍的 int 值也是可以的,你可以這樣寫:

for (int i = 0; i < 10; i++) {        int number = (int) (Math.random() * 100);    System.out.println("生成隨機數:" + number);}

以上程序的執行結果為:

③ 實現原理

通過分析 Math 的源碼我們可以得知:當第一次調用 Math.random() 方法時,自動創建了一個偽隨機數生成器,實際上用的是 new java.util.Random(),當下一次繼續調用 Math.random() 方法時,就會使用這個新的偽隨機數生成器。

源碼如下:

public static double random() {    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();}
privatestaticfinalclass RandomNumberGeneratorHolder { staticfinal Random randomNumberGenerator = new Random();}


總結


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

參考 & 鳴謝:

☞Windows 11 預覽版洩露!有 macOS 那味兒了.

☞賈伯斯居然是這樣面試我的,你能挺到哪一步?

相關焦點

  • Java 生成隨機數的 5 種方式,你知道幾種?
    方法是 的,因此在多線程情況下,只有一個線程會負責創建偽隨機數生成器(使用當前時間作為種子),其他線程則利用該偽隨機數生成器產生隨機數。Java生成隨機數的幾種高級用法,這篇推薦看一下。 因此 方法是線程安全的。
  • java生成隨機數的五種方法
    當第一次調用 Math.random() 方法時,自動創建了一個偽隨機數生成器,實際上用的是 new java.util.Random()。當接下來繼續調用 Math.random() 方法時,就會使用這個新的偽隨機數生成器。
  • Java隨機數的幾種有趣用法
    眾所周知,隨機數是任何一種程式語言最基本的特徵之一。而生成隨機數的基本方式也是相同的:產生一個0到1之間的隨機數。
  • 淺談Java中的幾種隨機數
    眾所周知,隨機數是任何一種程式語言最基本的特徵之一。而生成隨機數的基本方式也是相同的:產生一個0到1之間的隨機數。看似簡單,但有時我們也會忽略了一些有趣的功能。我們從書本上學到什麼?最明顯的,也是直觀的方式,在Java中生成隨機數隻要簡單的調用:java.lang.Math.random() 在所有其他語言中,生成隨機數就像是使用Math工具類,如abs, pow, floor, sqrt和其他數學函數。
  • 簡要解析:Java中隨機數生成的代碼實現
    double rand = Math.random();        通過Random類的對象  程序可生成許多不同類型的隨機數字,做法很簡單,只需調用方法nextInt()和nextFloat()即可(也可以調用nextLong()或者nextDouble())。
  • 教你在 Excel 中生成隨機數的 5 種方法
    1/ 使用 RAND 函數生成隨機數我將向您展示的第一種方法是在 Excel 中生成隨機值的最簡單方法。Excel中有一個非常簡單的RAND函數,它不需要參數,會生成一個 0 到 1 之間的隨機數。假設需要生成-3到4之間的隨機整數,那麼上面的公式就是你所需要的將 RAND 函數乘以 7 將產生 0 到 7 之間的隨機數。將 -3 添加到結果並四捨五入到零小數位,這將給出 -3 到 4 的隨機整數範圍。
  • 隨機數生成
    如利用蒙特卡羅法估計測量數據的不確定時,就需要使用隨機數生成器來傳遞分布。本書中大量的例子都用到了示例數據,創建這些數據就利用了隨機數生成器。Igor具有強大的統計分析功能,提供了大量函數和命令,用於分布計算、參數估計、假設檢驗、回歸分析等。其中僅偽隨機數生成器就有12種,可以生成滿足常見分布要求的隨機數,如二項分布隨機數、均勻分布隨機數、高斯分布隨機數、洛倫茲分布隨機數等。
  • 隨機數大家都會用,但是你知道生成隨機數的算法嗎?
    再不濟我們每周的抽獎都是用隨機數抽出來的,我們用隨機數的時候,往往都會加一個前綴,說它是偽隨機數,那麼這個偽隨機數的偽字該怎麼解釋,什麼又是真隨機數呢?真偽隨機數目前學界劃分真偽隨機數的方式非常簡單,一句話就能說明白,凡是用一定的算法使用程序生成的都是偽隨機數,通過物理現象產生的隨機數才是真隨機數。
  • JavaScript用Math.random()生成隨機數
    Math.random()的語法結構如下所示,可以看出它是沒有參數的。Math.random();JavaScript引擎會自動設置隨機數種子(seed),而沒有提供任何途徑讓我們自己來完成這件事,但某些其它語言(比如C++,Java)則通常允許用戶自主設置隨機數種子。2.
  • Excel隨機數生成方法,包括準確生成不重複和小數隨機數
    在 Excel 中,生成隨機數有兩個函數,分別為Rand函數和RandBetween函數,前者用於生成 0 到 1 之間的隨機數,後者用於生成指範圍的隨機數。它們生成的隨機數中都會產生重複值,如果要生成不重複的隨機數得用變通的方法,通常有兩種方法,一種為先生成種子再生成不重複的隨機數,另一種為用多個函數生成。
  • excel隨機數函數是什麼?excel怎樣生成隨機數?
    本篇將介紹excel隨機數函數是什麼?excel怎樣生成隨機數?有興趣的朋友可以了解一下!一、前言excel是我們工作中很常用的一款表格製作工具,它不僅僅只是用來製作表格,它還能對表格中的數據進行處理(比如:運算、排序、篩選等)。
  • Excel生成隨機數的技巧,隨機數發生器,你用過嗎
    商業分析中的很多應用要求從特定的概率分布中隨機抽樣。例如,在財務模型中,當銷售額、銷量增長幅度、運營支出和通貨膨脹因子等全都不確定時,我們可能對累積的貼現現金流的分布感興趣,而這一分布可以用概率分布來描述。那些決策模型的結果變量,是隨機輸入變量的複雜函數。要理解這些變量的概率分布,只有藉助所謂蒙特-卡羅方法的抽樣流程才能實現這一抽樣流程。
  • 密碼學的骰子——隨機數
    做開發的工程師們應該或多或少都接觸過隨機數,可能認為它就是一個隨機生成的數字嘛,使用時也很簡單,只要調用開發語言提供的函數即可。但實際上隨機數後面還是有著比較複雜但也有趣的知識點的。像常用到的C語言的rand庫和Java的java.util.Random類,就是採用了線性同餘算法生成隨機數。雖然名字好像不好聽,但偽隨機數已經滿足大多數應用場景的需求了。但對於密碼學來講,偽隨機數就遠遠不夠了。除了隨機性,密碼學要求的隨機數還要具備不可預測性。我們把具有這兩個性質的隨機數叫做密碼學安全的偽隨機數或強偽隨機數。
  • 探索 Java 各種隨機函數
    注意,本教程中的隨機函數均是形參為整形,返回值為區間[0,1)內的單精浮點數的函數。測試均為1~10000的隨機數生成速度測試(1D – 輸入x、2D – 輸入x, y)。更新記錄2016.1.22 – 初稿。2016.1.28(1) – 更新了Wichman-Hill隨機數的算法,修改內容。
  • 「每日一練」巧用python生成隨機數
    隨機數在我們的生產和生活中有很多的應用場景,比如說登錄驗證的隨機數字等等,那麼你知道在Python中怎麼生成隨機數嗎?往下看,就是這麼簡單!>隨機整數:random.randint(a,b),生成區間內的整數隨機小數:習慣用numpy庫,利用np.random.randn(5)生成5個隨機小數0-1隨機小數:random.random(),括號中不傳參第一行:import
  • 如何用excel批量生成正態分布的隨機數?
    提到用rand函數批量生成隨機數,這是最簡單最快速的方法,但同時也有一個缺陷,就是rand函數生成的是均勻分布的隨機數,在區間範圍內會分布得比較均勻,不符合日常生活中數據的按照正態分布的特點,這樣也就留下了明顯的痕跡。比如我用rand函數生成0-100的100個隨機數,用Minitab自動生成直方圖,頻數是下面這樣的:大家可以看到,大致上每個區間分布的數據都比較均勻。
  • 隨機生成N個立方體,學習blender Python隨機數生成
    打開blender控制臺首先輸入import random print(random.randint(0,9))多次執行print(random.randint(0,9)),就能見到系統每次都生成0-9內的隨機整數使用了
  • python:random --- 生成隨機數
    random --- 生成隨機數生成隨機小數import randomprint(random.random())0.7195625930629974Process finishedwith exit code 0import randomprint(random.uniform(1, 2))1.4247584611406854Process finished with exit code 0生成隨機整數
  • 【Excel】隨機數:生成不重複隨機整數的技巧
    在 Excel 中生成隨機數的函數不少,例如:Rand、Randbetween……..但是這個…會出現重複的呀
  • 用公式生成不重複的隨機數字
    在某些場合我們需要生成一組不重複的隨機數字,提到隨機,我們肯定能想到RANDBETWEEN函數,當然這個函數是可以生成一定範圍的隨機數