有一次,我在逛 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,傳送門~
我是沉默王二,一枚有趣的程序員。原創不易,莫要白票,請你爲本文點個贊吧,這將是我寫做更多優質文章的最強動力。