隨機數在驗證碼、Session ID、負載均衡權重、加密金鑰、模擬測試等場景無處不在。Java 提供了四大核心 API,各有優劣與適用邊界。本文從**演算法原理、執行緒安全、效能基準、安全性等級**四個維度深度對比,給出選型決策樹與生產級最佳實踐。
| API | 演算法 | 執行緒安全 | 效能 | 安全性 | 典型場景 |
|---|---|---|---|---|---|
Math.random() | LCG (Linear Congruential Generator) | 內部同步鎖 (低併發瓶頸) | 低 | ❌ 可預測 | 簡單測試、單執行緒腳本 |
java.util.Random | LCG (48-bit 種子) | CAS + volatile (中等爭用) | 中 | ❌ 可預測 | 一般業務邏輯、非安全隨機 |
ThreadLocalRandom (JDK 7+) | LCG (每執行緒獨立種子) | ✅ 無鎖 (ThreadLocal) | **高** (無爭用) | ❌ 可預測 | **高併發非安全隨機首選** |
SecureRandom | OS 熵源 / SHA1PRNG / DRBG | 內部同步 (較慢) | 低 | ✅ **加密級不可預測** | Token、Key、Salt、Session ID、OAuth state |
// 區間整數 [origin, bound) (含 origin、不含 bound)
int randInt = ThreadLocalRandom.current().nextInt(10, 20); // 10~19
// 區間長整數
long randLong = ThreadLocalRandom.current().nextLong(1000L, 2000L);
// 區間浮點數 [origin, bound)
double randDouble = ThreadLocalRandom.current().nextDouble(5.0, 10.0);
// 布林值
boolean flag = ThreadLocalRandom.current().nextBoolean();
// 陣列隨機打亂 (Fisher-Yates)
void shuffle(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
int j = ThreadLocalRandom.current().nextInt(i + 1);
int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
}
}
// 獲取強隨機實例 (優先使用 OS 熵源)
SecureRandom sr = new SecureRandom(); // 無參構造自動選擇最強算法
// 生成 256-bit (32 bytes) 隨機金鑰
byte[] aesKey = new byte[32];
sr.nextBytes(aesKey);
// 生成 URL-safe Base64 Token (Session ID, OAuth state)
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(aesKey);
// 生成指定範圍安全整數 [0, bound)
int secureInt = sr.nextInt(1_000_000);
// ⚠️ 避免指定算法名稱,讓 JVM 選擇平台最強提供者
// SecureRandom.getInstance("SHA1PRNG") // 可能阻塞、效能差
// 固定種子 → 序列完全可重現 (單元測試、壓測回放)
Random fixed = new Random(12345L);
for (int i = 0; i < 5; i++) System.out.print(fixed.nextInt(100) + " ");
// 輸出固定:51 80 41 28 55 ...
// 無種子 → 種子 = System.nanoTime() + threadId + counter (JDK 8+)
// 每次運行序列不同
Random dynamic = new Random();
| 需求 | ThreadLocalRandom / Random | Math.random() |
|---|---|---|
| 整數 [0, n) | nextInt(n) | (int)(Math.random() * n) |
| 整數 [min, max] (含兩端) | nextInt(max - min + 1) + min | (int)(Math.random() * (max - min + 1)) + min |
| 長整數 [0, n) | nextLong(n) (JDK 8+) | 不支援 |
| 浮點數 [0.0, 1.0) | nextDouble() | Math.random() |
| 浮點數 [min, max) | nextDouble(max - min) + min | Math.random() * (max - min) + min |
| 高斯分布 (μ=0, σ=1) | nextGaussian() | 不支援 |
Benchmark Mode Cnt Score Error Units
ThreadLocalRandom.nextInt avgt 10 12.3 ± 0.2 ns/op ← 最快
Random.nextInt (單執行緒) avgt 10 18.7 ± 0.3 ns/op
Math.random avgt 10 45.6 ± 0.8 ns/op ← 鎖爭用
SecureRandom.nextInt avgt 10 850.0 ± 15.0 ns/op ← 熵源收集
ThreadLocalRandom.nextInt (8執行緒) avgt 10 13.1 ± 0.2 ns/op ← 線性擴展
Random.nextInt (8執行緒) avgt 10 320.0 ± 20.0 ns/op ← CAS 爭用劇增
需要加密安全性 (Token/Key/Salt)?
├─ 是 → SecureRandom (唯一選擇)
└─ 否 → 高併發 (多執行緒熱點)?
├─ 是 → ThreadLocalRandom (JDK 7+ 首選)
└─ 否 → 單執行緒 / 簡單邏輯 / 測試代碼?
├─ 需可重現序列 → new Random(seed)
└─ 一般用途 → ThreadLocalRandom / new Random()
public final class RandomUtils {
private static final SecureRandom SECURE = new SecureRandom();
private static final String ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private RandomUtils() {}
/** 生成指定長度 URL-safe Token (Session ID, API Key) */
public static String secureToken(int bytes) {
byte[] buf = new byte[bytes];
SECURE.nextBytes(buf);
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}
/** 生成指定長度字母數字字串 (驗證碼、邀請碼) */
public static String alphanumeric(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(ALPHANUM.charAt(ThreadLocalRandom.current().nextInt(ALPHANUM.length())));
}
return sb.toString();
}
/** 產生 [min, max] 區間安全整數 */
public static int secureIntInRange(int min, int max) {
if (min > max) throw new IllegalArgumentException();
return min + SECURE.nextInt(max - min + 1);
}
/** Fisher-Yates 洗牌 (原地、O(n)、無偏) */
public static void shuffle(int[] array) {
for (int i = array.length - 1; i > 0; i--) {
int j = ThreadLocalRandom.current().nextInt(i + 1);
int tmp = array[i]; array[i] = array[j]; array[j] = tmp;
}
}
}
Math.random() 於多執行緒熱點:內部 synchronized 會成為瓶頸。Random 產生 Session ID / API Key / 重設密碼 Token:LCG 可預測,攻擊者可反推種子竊取帳號。SecureRandom.getInstance("SHA1PRNG"):Linux 上可能阻塞等待熵源;無參構造讓 JVM 選擇 NativePRNGNonBlocking 或 DRBG。ThreadLocalRandom:無鎖、效能最佳、API 豐富 (支援 nextLong(bound)、nextDouble(origin, bound))。new Random(42L) 確保測試確定性、可回放。💡 核心原則:業務邏輯用 ThreadLocalRandom,安全相關用 SecureRandom,測試用固定種子 Random,忘掉 Math.random()。