Java 提供多層次隨機數 API,從簡單的 `Math.random()` 到密碼學級的 `SecureRandom`。選擇錯誤會導致**效能瓶頸、線程爭用、安全漏洞、統計偏差**。本文系統對比四大核心 API,給出生產環境選型指南。
| API | 類別 | 執行緒安全 | 演算法 | 速度 | 安全性 | 適用場景 |
|---|---|---|---|---|---|---|
| `Math.random()` | 靜態方法 | 內部同步 (鎖爭用) | LinearCongruentialGenerator (LCG) | 慢 (同步開銷) | 低 (可預測) | 簡單測試、單執行緒、非關鍵 |
| `java.util.Random` | 類 | 同步方法 (鎖爭用) | LCG (48 位種子) | 中 | 低 (可預測) | 一般模擬、單執行緒、非安全 |
| `ThreadLocalRandom` | 類 (JDK 7+) | **無鎖、執行緒本地** | LCG (64 位種子) | **快 (無同步)** | 低 | **高併發、非安全、JDK 7+ 首選** |
| `SecureRandom` | 類 | 同步 | OS 熵源 / DRBG (SHA1PRNG 等) | 慢 (熵收集) | **高 (密碼學級)** | **Token、Key、Session ID、加密** |
// 返回 [0.0, 1.0) double
double d = Math.random();
// 常見轉換陷阱:生成 [min, max] 整數
// 錯誤:(int)(Math.random() * max) + min // 分佈不均
// 正確:[min, max] 閉區間
int randomInt = (int)(Math.random() * (max - min + 1)) + min;
// 正確:[min, max) 半開區間
int randomInt = (int)(Math.random() * (max - min)) + min;
缺陷: 內部單例 `Random` 共享,多執行緒嚴重鎖爭用,JDK 8+ 已不推薦高併發使用。
Random r = new Random(); // 系統時間種子
Random r2 = new Random(12345L); // 固定種子 → 確定性序列 (測試/重現)
// 常用方法
boolean b = r.nextBoolean(); // 50% true
double d = r.nextDouble(); // [0.0, 1.0)
float f = r.nextFloat(); // [0.0, 1.0f)
int i = r.nextInt(); // [-2^31, 2^31-1]
int bounded = r.nextInt(100); // [0, 100) 均勻分佈
long l = r.nextLong(); // 全長整數範圍
byte[] bytes = new byte[16]; r.nextBytes(bytes); // 填充位元組陣列
// 指定區間 [min, max]
int inRange = r.nextInt(max - min + 1) + min; // 閉區間
// 避免:r.nextInt() % n + min (負數餘數導致分佈偏移)
種子可重現性: 相同種子 `new Random(seed)` 產生**完全相同序列**,利於單元測試、模擬重現、除錯。
// 直接使用靜態方法,無需實例化
int i = ThreadLocalRandom.current().nextInt(100); // [0, 100)
long l = ThreadLocalRandom.current().nextLong(1000, 2000); // [1000, 2000) JDK 8+
double d = ThreadLocalRandom.current().nextDouble(0.5, 1.5); // [0.5, 1.5) JDK 8+
// 併發效能提升 10-100x (無鎖、無緩存行爭用)
// 內部每執行緒獨立種子,自動初始化 (混合系統熵、執行緒 ID、時間)
原理: `ThreadLocal
// 獲取預設強隨機實例 (通常採用 OS 熵源: /dev/urandom, Windows CryptoAPI)
SecureRandom sr = new SecureRandom();
// 生成安全 Token (32 位元組 = 256 位熵)
byte[] token = new byte[32];
sr.nextBytes(token);
String secureToken = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
// 生成 AES-256 金鑰
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256, sr);
SecretKey key = kg.generateKey();
// 種子管理:生產環境**不要**手動 setSeed!依賴 OS 熵源自動播種
// sr.setSeed(...) 會降低熵品質,僅用於測試重現
效能提示: `SecureRandom` 首次調用可能阻塞等待熵池 (Linux `/dev/random` 行為),建議啟動期預熱或使用 `-Djava.security.egd=file:/dev/./urandom` (非阻塞)。
public final class RandomUtils {
private RandomUtils() {}
// 非安全、高併發首選
public static int nextInt(int minInclusive, int maxInclusive) {
return ThreadLocalRandom.current().nextInt(minInclusive, maxInclusive + 1);
}
public static long nextLong(long minInclusive, long maxInclusive) {
return ThreadLocalRandom.current().nextLong(minInclusive, maxInclusive + 1);
}
public static double nextDouble(double minInclusive, double maxExclusive) {
return ThreadLocalRandom.current().nextDouble(minInclusive, maxExclusive);
}
// 安全級 (Token、Key、Session)
public static String secureToken(int byteLength) {
byte[] bytes = new byte[byteLength];
SecureRandomHolder.INSTANCE.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
public static byte[] secureBytes(int length) {
byte[] bytes = new byte[length];
SecureRandomHolder.INSTANCE.nextBytes(bytes);
return bytes;
}
// 延遲初始化 SecureRandom 避免啟動阻塞
private enum SecureRandomHolder {
INSTANCE;
private final SecureRandom sr;
SecureRandomHolder() {
// 可指定演算法: "NativePRNGNonBlocking" (Linux), "Windows-PRNG"
this.sr = new SecureRandom();
}
}
}
需要密碼學安全嗎?
├─ 是 → SecureRandom (Token、Key、Session ID、Nonce、加密隨機數)
└─ 否 → 高併發環境 (Web 容器、並行流、多執行緒)?
├─ 是 → ThreadLocalRandom (JDK 7+ 標準、零鎖、高吞吐)
└─ 否 → 單執行緒/測試/需固定種子重現?
├─ 需固定種子 → new Random(seed)
└─ 簡單臨時用 → Math.random() (不推薦生產)
| 錯誤代碼 | 問題 | 修正 |
|---|---|---|
| `new Random().nextInt(10)` 在循環中 | 頻繁創建對象、種子可能重複 (系統時間粒度) | 複用實例或用 `ThreadLocalRandom` |
| `Math.abs(r.nextInt() % n)` | `Integer.MIN_VALUE` 絕對值仍為負、分佈偏移 | `r.nextInt(n)` 或 `ThreadLocalRandom.current().nextInt(n)` |
| `SecureRandom` 用於大量非安全隨機 | 極慢 (熵收集、同步) | 改用 `ThreadLocalRandom` |
| `sr.setSeed(System.currentTimeMillis())` | 大幅降低熵品質、可預測 | **永不**手動播種 SecureRandom (除非測試) |
| 並行流中用共享 `Random` | 嚴重鎖爭用、效能崩潰 | `ThreadLocalRandom` 或 `SplittableRandom` |
// 可分割的隨機產生器,專為並行流設計
SplittableRandom sr = new SplittableRandom();
IntStream ints = sr.ints(1_000_000, 0, 100); // 產生 100 萬 [0,100) 整數流
// 可分割:sr.split() 產生獨立子產生器,避免競爭
總結:**非安全高併發用 `ThreadLocalRandom`,安全關鍵用 `SecureRandom`,測試重現用 `new Random(seed)`,拋棄 `Math.random()` 和共享 `Random` 實例。** 正確選型能同時提升效能、安全性與代碼可維護性。