本文分析一種不依賴 JDK 內建隨機函數、純利用多線程調度非確定性構建的隨機數生成器。雖然生產不可用,但作為「系統熵源挖掘」的教學案例極具啟發性。
┌─────────────────────────────────────────────────────────────┐
│ 共享狀態(靜態字段) │
│ - R[65536] : LCG 生成的查找表 │
│ - f (AtomicInteger) : 當前索引指針 │
│ - randomValue : 最新隨機值 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 工作線程 (Runnable) │
│ while (!interrupted) { │
│ f = (f/2 + R[f]) % 65536 // 狀態遷移 │
│ randomValue = R[f] // 輸出 │
│ } │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 主控流程 (13 輪疊代放大) │
│ for i in 0..12: │
│ 啟動工作線程 → sleep(極短) → interrupt → 采樣 randomValue │
│ 動態調整參數 (x, shuffleR) │
│ 檢測不動點 → 強制打亂 │
│ 返回第 13 次采樣 │
└─────────────────────────────────────────────────────────────┘
R[0] = 13849;
for i = 1 to 65535:
R[i] = (2053 * R[i-1] + 13849) % 65536
// 參數選擇:m=2^16, a=2053, c=13849 滿足 Hull-Dobell 定理,周期最大 65536
生成 0~65535 的全排列(若參數得當),作為確定性查找表。
f_{next} = (f/2 + R[f]) mod M
f/2:右移一位,引入低位變化R[f]:查表引入非線性| 輪次 | 熵源 | 放大機制 |
|---|---|---|
| 1-3 | 線程調度抖動 | 微小時間差導致 f 偏移不同 |
| 4-8 | 參數動態調整 | x = f%13, shuffleR(11+f/7) 依賴前輪結果 |
| 9-13 | 不動點檢測 | 連續相同則 shuffleR(13+f/11) 強制跳出 |
void shuffleR(int shift) {
int[] copy = R.clone();
for i in 0..M-1: R[i] = copy[(i + shift) % M];
}
O(M) 操作但 M=65536 固定,實際耗時 ~0.1ms。改變查找表順序,打破潛在週期。
ThreadLocalRandom 單次 ~1ns。慢 1000 萬倍。f、randomValue、R[],並發調用完全亂序。interrupt() 不保證線程即時停止,大量調用導致線程積壓。若因特殊原因不能用 Math.random()(Random 實例),請用:
// 1. ThreadLocalRandom (JDK 7+) - 最推薦
ThreadLocalRandom.current().nextDouble();
// 2. SecureRandom - 加密級
new SecureRandom().nextDouble();
// 3. 自定義種子 Random - 可復現
new Random(12345L).nextDouble();
// 4. SplittableRandom (JDK 8+) - 並行流
new SplittableRandom().nextDouble();
// 5. 純算法實現 (Xorshift/Xoshiro) - 無依賴、極快
// 參考 Vigna 的論文,幾行代碼實現高質量 PRNG
該算法雖不可用,但展示了隨機數生成的核心哲學:
現代 CPU 指令集(RDRAND、RDSEED)直接提供硬件熵源,OS (/dev/urandom, CryptGenRandom) 做提取白化,JDK SecureRandom 封裝調用。理解這一層次,才算真正懂隨機數。