從簡單的驗證碼到加密級密鑰,Java 提供四大隨機數 API。本文對比性能、線程安全、統計質量、安全性四個維度,給出場景化選型建議。
| API | 底層算法 | 線程安全 | 統計質量 | 加密安全 | 典型場景 |
|---|---|---|---|---|---|
| Math.random() | Linear Congruential (LCG) | 是 (內部同步) | 低 (周期短、位相關) | 否 | 簡單測試、教學演示 |
| java.util.Random | LCG (48 位種子) | 是 (CAS 更新種子) | 中 | 否 | 一般業務、非安全隨機 |
| ThreadLocalRandom (JDK 7+) | LCG (64 位種子) | 極致 (ThreadLocal 隔離) | 中高 | 否 | 高併發、大量隨機數 |
| SecureRandom | OS 熵源 (SHA1PRNG, NativePRNG) | 是 | 高 (經 NIST 測試) | 是 | 密鑰、Token、Session ID、加密 |
// 1. Math.random() - 最簡單但受限
double d = Math.random(); // [0.0, 1.0)
int n = (int) (Math.random() * 10); // [0, 9] 有偏差見下文
// 2. Random - 經典選擇
Random r = new Random();
int i = r.nextInt(10); // [0, 9] 均勻
long l = r.nextLong();
boolean b = r.nextBoolean();
double g = r.nextGaussian(); // 高斯分佈 μ=0, σ=1
// 3. ThreadLocalRandom - 高併發首選 (JDK 7+)
int i = ThreadLocalRandom.current().nextInt(10);
long l = ThreadLocalRandom.current().nextLong(100, 200); // [100, 200) JDK 8+
// 4. SecureRandom - 安全級
SecureRandom sr = new SecureRandom();
byte[] key = new byte[32];
sr.nextBytes(key); // 256 位 AES 密鑰
String token = new BigInteger(130, sr).toString(32); // URL 安全隨機字符串
// 錯誤:Math.round(Math.random() * 10) -> 0 和 10 概率是 1-9 的一半
// 正確:Math.floor(Math.random() * 11) -> [0, 10] 均勻
// 或直接用 Random/ThreadLocalRandom.nextInt(11)
// 危險:Math.abs(r.nextInt()) % n -> 當 nextInt() == Integer.MIN_VALUE 時 abs 仍為負
// 正確:r.nextInt(n) 內部已處理拒絕採樣偏差
// 種子可預測:new Random() 使用 System.nanoTime(),啟動瞬間可被推測
// 安全場景必須用 SecureRandom
// 差:共享實例,CAS 重試開銷大
static final Random SHARED = new Random();
// 好:ThreadLocalRandom 完全無鎖
ThreadLocalRandom.current().nextInt();
// 或 ThreadLocal 包裝
static final ThreadLocal LOCAL = ThreadLocal.withInitial(Random::new);
// JDK 8+ ThreadLocalRandom
ThreadLocalRandom.current().nextInt(min, max + 1); // 含上界
// 通用工具方法
public static int randomInt(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
// 字母數字 6 位驗證碼
String code = ThreadLocalRandom.current()
.ints(6, 0, 36) // 0-35
.mapToObj(i -> Character.toString(i < 10 ? '0' + i : 'A' + i - 10))
.collect(Collectors.joining());
// URL 安全 Token (RFC 4648 Base64url)
SecureRandom sr = new SecureRandom();
byte[] bytes = new byte[24];
sr.nextBytes(bytes);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
// 簡單版:前綴和 + 二分查找
public static T weightedRandom(List items, List weights) {
double[] prefix = new double[weights.size()];
prefix[0] = weights.get(0);
for (int i = 1; i < weights.size(); i++) prefix[i] = prefix[i-1] + weights.get(i);
double r = ThreadLocalRandom.current().nextDouble() * prefix[prefix.length-1];
int idx = Arrays.binarySearch(prefix, r);
return items.get(idx < 0 ? -idx - 1 : idx);
}
// 固定種子
Random deterministic = new Random(12345L); // 每次運行序列相同
// 用於單元測試、壓力測試復現、蒙特卡洛模擬驗證
需要加密安全?
├─ 是 → SecureRandom (唯一選擇)
└─ 否 → 高併發 (多線程大量調用)?
├─ 是 → ThreadLocalRandom.current()
└─ 否 → 簡單偶爾用?
├─ 是 → Math.random() / new Random()
└─ 否 → 需可重現/特定分佈?
├─ 是 → new Random(seed) + 自定義算法
└─ 否 → ThreadLocalRandom (通用最優)