恕我直言,我懷疑你並不會生成隨機數

有一次,我在逛 Stack Overflow 的時候,發現有這樣一個問題:「Java 中如何產生一個指定範圍內的隨機數」,我心想,「就這破問題,居然有 398 萬的閱讀量,統計肯定沒搞錯?不就一個 Math.random() 的事兒嘛。」java

因而我直接動用本身的權力投了一票反對。結果,沒等到權力執行後的喜悅,卻收到了一條提醒:「聲望值低於 125 的人有投票權,但不會公開顯示。」我呀,我去,扎心了。就衝我這急脾氣,不用代碼證實一下本身的實力,我還有臉說本身有十年的開發經驗嗎?因而我興沖沖地就打開 IDEA,敲下了下面這段代碼:git

public class GenerateMathRandomInteger {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Runnable r = () -> {
            int generatedInteger = leftLimit + (int) (Math.random() * rightLimit);
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

這段代碼我寫得沒毛病吧?乍看上去,參數和類的命名都很合理,就連 Lambda 表達式也用上了。但程序輸出的結果卻出乎個人意料:程序員

8
10
10
4
3
4
6
12
3
複製代碼

12 是從哪裏蹦出來的?固然是從程序的 bug 裏蹦出來。leftLimit + (int) (Math.random() * rightLimit) 生成的隨機數可能超出指定的範圍。不行,Math.random() 信不過,必需要換一種方法。靈機一動,我想到了 Random 類,因而我寫下了新的代碼:github

public class GenerateRandomInteger {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Random random = new Random();
        int range = rightLimit - leftLimit + 1;

        Runnable r = () -> {
            int generatedInteger = leftLimit + random.nextInt() % range;
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

這一次,我滿懷信心,Math.random() 解決不了的問題,random.nextInt() 就必定可以解決。結果,輸出結果再次啪啪啪打了我這張帥臉。apache

0
-3
10
2
2
-4
-4
-6
6
複製代碼

居然還有負數,這真的是殘酷的現實,我被教育了,彷佛找回了剛入職那會被領導蹂躪的感受。幸虧,個人心態已經不像年輕時候那樣易怒,穩得一匹:出問題沒關係,找解決方案就對了。bash

因而 5 分鐘後我寫出了下面這段代碼:微信

public class GenerateRandomInteger {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Random random = new Random();
        int range = rightLimit - leftLimit;

        Runnable r = () -> {
            int generatedInteger = leftLimit + (int)(random.nextFloat() * range);
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

不管是調整線程的數量,仍是屢次從新運行,結果都符合預期,在 2 - 11 之間。多線程

7
2
5
8
6
2
9
9
7
複製代碼

nextFloat() 方法返回一個均勻分佈在 0 - 1 之間的隨機浮點數(包含 0.0f,但不包含 1.0f),乘以最大值和最小值的差,再強轉爲 int 類型就能夠保證這個隨機數在 0 到(最大值-最小值)之間,最後再加上最小值,就剛好能夠獲得指定範圍內的數字。dom

若是你肯讀源碼的話,會發現 Random 類有一個 nextInt(int bound) 的方法,該方法會返回一個隨機整數,均勻分佈在 0 - bound 之間(包含 0,但不包含指定的 bound)。那麼利用該方法也能夠獲得一個有效的隨機數,來看示例代碼。測試

public class GenerateRandomNextInt {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Random random = new Random();
        Runnable r = () -> {
            int generatedInteger = leftLimit + random.nextInt(rightLimit - leftLimit + 1);
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

因爲 nextInt() 不包含 bound,所以須要 + 1。程序運行的結果也符合預期:

8
2
9
8
4
6
4
5
7
複製代碼

你看,我以前兩次嘗試都以失敗了結,但我仍然沒有放棄但願,通過本身的深思熟慮,我又找到了兩種可行的解決辦法。這讓我想起了普希金的一首詩歌:

假如生活欺騙了你,不要悲傷,不要心急,憂鬱的日子裏須要鎮靜,一切都會過去,一切都是瞬息,一切都會過去。但願之火須要再燃,須要呵護,不致讓暴風雨將其熄滅,不致讓本身在黑暗、陰冷、無助中絕望。

一首好詩吟完以後,咱們再來想一想還有沒有其餘的方案。反正我是想到了,Java 7 之後可使用 ThreadLocalRandom 類,代碼示例以下:

public class GenerateRandomThreadLocal {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Runnable r = () -> {
            int generatedInteger = ThreadLocalRandom.current().nextInt(leftLimit, rightLimit +1);
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

程序輸出的結果以下:

11
9
6
10
6
6
10
7
3
複製代碼

ThreadLocalRandom 類繼承自 Random 類,它使用了內部生成的種子來初始化(外部沒法設置,因此不能再現測試場景),而且不須要顯式地使用 new 關鍵字來建立對象(Random 能夠經過構造方法設置種子),能夠直接經過靜態方法 current() 獲取針對本地線程級別的對象:

static final ThreadLocalRandom instance = new ThreadLocalRandom();

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    U.putLong(t, SEED, seed);
    U.putInt(t, PROBE, probe);
}

public static ThreadLocalRandom current() {
    if (U.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}
複製代碼

這樣作的好處就是,在多線程或者線程池的環境下,能夠節省沒必要要的內存開銷。

最後,我再提供一個解決方案,使用 Apache Commons Math 類庫的 RandomDataGenerator 類。在使用該類庫以前,須要在 pom.xml 文件中引入該類庫的依賴。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.6.1</version>
</dependency>
複製代碼

在須要生成指定範圍的隨機數時,使用 new RandomDataGenerator() 獲取隨機生成器實例,而後使用 nextInt() 方法直接獲取最大值與最小值之間的隨機數。來看示例。

public class RandomDataGeneratorDemo {
    public static void main(String[] args) {
        int leftLimit = 2;
        int rightLimit = 11;

        Runnable r = () -> {
            int generatedInteger = new RandomDataGenerator().nextInt( leftLimit,rightLimit);
            System.out.println(generatedInteger);
        };
       for (int i = 1; i < 10; i++) {
           new Thread(r).start();
       }
    }
}
複製代碼

輸出結果以下所示:

8
4
4
4
10
3
10
3
6
複製代碼

結果徹底符合咱們的預期——這也是個人最後一招,沒想到就這麼愉快地全交給你了。

好了,我親愛的讀者朋友,以上就是本文的所有內容了。若是以爲文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。示例代碼已經上傳到 GitHub,傳送門~

我是沉默王二,一枚有趣的程序員。原創不易,莫要白票,請你爲本文點個贊吧,這將是我寫做更多優質文章的最強動力。

相關文章
相關標籤/搜索