「生成隨機數」看似簡單,實則暗藏效能陷阱與分布不均風險。本文梳理 Java 歷代隨機數 API 演進脈絡,對比 Math.random()、java.util.Random、ThreadLocalRandom 三代方案的優劣,並揭示 Math.round(Math.random()*n) 導致的分布偏差、Math.abs(rnd.nextInt())%n 的模運算陷阱,給出生產環境最佳實踐。
double d = Math.random(); // [0.0, 1.0)
double d10 = Math.random() * 10; // [0.0, 10.0)
int i10 = (int)(Math.random() * 10); // [0, 9] 均勻分布
Random,多線程爭用 CAS 更新種子,高併發下成為熱點。double,其他類型需轉換。Random rnd = new Random();
int i = rnd.nextInt(10); // [0, 9]
long l = rnd.nextLong();
boolean b = rnd.nextBoolean();
double g = rnd.nextGaussian(); // 高斯分布
AtomicLong 種子),但多線程競爭同一實例時 CAS 重試開銷大。new Random() 開銷大,通常做成 static final 單例或 ThreadLocal。// 無需實例化,靜態調用,當前線程專屬種子,零競爭
int i = ThreadLocalRandom.current().nextInt(10);
long l = ThreadLocalRandom.current().nextLong(100, 200); // [100, 200)
double d = ThreadLocalRandom.current().nextDouble();
Random。.ints()。ThreadLocalRandom。Math.random()*10 產生 [0,10),round 使 0.0~0.499→0,0.5~1.499→1,... 9.5~9.999→10。0 和 10 的概率只有內部整數的一半。
正確寫法:
// 產生 [0, n] 均勻整數
int r = (int)(Math.random() * (n + 1)); // 或 Math.floor(Math.random() * (n+1))
// 產生 [0, n) 均勻整數
int r = (int)(Math.random() * n);
nextInt() 範圍 [-2^31, 2^31-1],abs 後 0 出現概率略大,且取模進一步放大偏差。
正確寫法:
// Random / ThreadLocalRandom 內建無偏區間方法
int r = rnd.nextInt(n); // [0, n)
int r = rnd.nextInt(lo, hi); // [lo, hi) JDK 17+
new SecureRandom().nextInt(n)。| 場景 | 推薦 API | 理由 |
|---|---|---|
| 單線程/低併發、簡單腳本 | Math.random() | 零依賴、一行代碼 |
| 多線程高併發、高性能要求 | ThreadLocalRandom.current() | 零競爭、API 豐富、JDK 7+ 標配 |
| 需可重現序列(測試、模擬) | new Random(seed) | 固定種子可復現 |
| 加密、Token、安全敏感 | SecureRandom | 密碼學安全 |