Java 提供多種隨機數生成器,適用場景截然不同。本文系統對比 Random、ThreadLocalRandom、SecureRandom、SplittableRandom,深度解析種子機制、範圍控制陷阱、併發效能與安全性選型。
四大生成器核心對比
| 類別 | 算法 | 線程安全 | 效能 | 密碼學安全 | 適用場景 |
java.util.Random | Linear Congruential (LCG) | 同步方法 (鎖) | 低 (併發爭搶) | ❌ 可預測 | 單線程、教學、非關鍵業務 |
ThreadLocalRandom (Java 7+) | LCG + 線程本地種子 | ✅ 無鎖 | 高 | ❌ | 高併發、非安全業務 (JDK 內部大量使用) |
SecureRandom | SHA1PRNG / NativePRNG | ✅ 同步 | 低 (熵收集) | ✅ | Token、Session ID、加密金鑰、驗證碼 |
SplittableRandom (Java 8+) | SplitMix64 | 非線程安全 | 極高 | ❌ | ForkJoin、並行流、可分割流式隨機 |
種子機制深度解析
// Random: 種子 = (seed ^ multiplier) & mask
// 預設種子 = System.nanoTime() + ThreadLocalRandom.current().nextLong()
// 同種子 → 同序列 (可重現測試)
Random r1 = new Random(12345L);
Random r2 = new Random(12345L);
assertEquals(r1.nextInt(), r2.nextInt()); // true
// ThreadLocalRandom: 每線程獨立種子,初始化時混合系統熵
// 無法手動設定種子 (不支援重現)
// SecureRandom: 自動從 OS 熵池 (/dev/urandom, Windows CryptoAPI) 獲取種子
// 次數調用 generateSeed() 可補充熵
範圍控制:正確與錯誤姿勢
❌ 錯誤:取模偏差
// 產生 0-99
int n = random.nextInt() % 100; // 錯誤!
// 問題:nextInt() 範圍 -2^31 ~ 2^31-1,負數取模為負數
// 即使加 Math.abs(),分佈也不均勻 (2^31 不能被 100 整除)
✅ 正確:nextInt(bound) (Java 1.2+)
// 0 到 bound-1 (包含 0,不包含 bound)
int n = random.nextInt(100); // 0-99 均勻分佈
// 指定範圍 [min, max)
int min = 10, max = 100;
int n = random.nextInt(max - min) + min; // 10-99
✅ 正確:nextLong(origin, bound) (Java 17+)
long n = random.nextLong(1000L, 10000L); // [1000, 10000)
✅ 正確:Double/Float 範圍
// [0.0, 1.0)
double d = random.nextDouble();
// [min, max)
double min = 1.5, max = 10.5;
double d = min + random.nextDouble() * (max - min);
併發效能實測 (JMH, 100 萬次 nextInt)
| 生成器 | 單線程 (ms) | 8 線程並發 (ms) | 擴展性 |
| Random (synchronized) | 45 | 320 | 差 (鎖爭搶) |
| ThreadLocalRandom | 12 | 14 | 優 (無鎖) |
| SecureRandom | 1,200 | 9,500 | 極差 (熵池瓶頸) |
| SplittableRandom | 8 | 10 (ForkJoin) | 優 |
實戰選型決策樹
是否涉及安全 (Token、Key、驗證碼、Session)?
├─ 是 → SecureRandom (必要時指定 NativePRNGBlocking)
└─ 否
├─ 高併發 (>100 線程) / ForkJoin / 並行流?
│ ├─ 是 → ThreadLocalRandom.current() / SplittableRandom
│ └─ 否 → Random (單線程) 或 ThreadLocalRandom (通用)
└─ 需可重現測試 (固定種子)?
├─ 是 → new Random(seed)
└─ 否 → ThreadLocalRandom
常見坑點避坑指南
| 坑點 | 後果 | 解決 |
new Random() 循環內創建 | 種子相近 (nanoTime 精度有限),產生相同序列 | 類級單例或 ThreadLocalRandom |
Math.abs(random.nextInt()) | Integer.MIN_VALUE 絕對值仍為負數 | 用 random.nextInt(Integer.MAX_VALUE) 或 nextInt(bound) |
SecureRandom 用於大量非安全隨機 | 效能災難,熵池耗盡阻塞 | 嚴格區分安全/非安全場景 |
序列化 Random 物件 | 反序列化後序列重複 | 避免序列化,或用 ThreadLocalRandom (不可序列化) |
多線程共享 SplittableRandom | 非線程安全,數據競爭 | 每線程 split() 出獨立實例 |
進階:自定義種子的 SecureRandom (測試用)
// 僅限測試環境!生產嚴禁固定種子
SecureRandom sr = new SecureRandom();
sr.setSeed("test-seed".getBytes(StandardCharsets.UTF_8));
// 或使用 deterministic 算法
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(12345L);