隨機數是程序設計中無處不在的基礎設施:洗牌、抽獎、搶紅包、購車搖號、密碼生成、負載均衡……本文系統梳理 Java 隨機數體系(Math.random、Random、ThreadLocalRandom、SecureRandom),揭示偽隨機原理,並通過五大實戰場景演示正確應用模式。
| 類/方法 | 包 | 特點 | 適用場景 |
|---|---|---|---|
Math.random() |
java.lang | 靜態方法,返回 [0,1) double,內部單例 Random | 簡單臨時需求,無并發壓力 |
java.util.Random |
java.util | 豐富 API(int/long/float/double/boolean/bytes),線程安全,種子可控 | 通用業務邏輯,需可重現隨機序列 |
ThreadLocalRandom |
java.util.concurrent | 線程本地實例,無競爭,性能優於 Random | 高并發環境,JDK 7+ 推薦 |
SecureRandom |
java.security | 密碼學強隨機,熵源收集,速度慢 | 加密金鑰、Token、Session ID、安全敏感場景 |
// 源碼邏輯
private static Random randomNumberGenerator;
public static double random() {
Random rnd = randomNumberGenerator;
if (rnd == null) rnd = initRNG(); // 雙重檢查單例
return rnd.nextDouble();
}
Random.nextDouble()Random rnd = new Random(); // 或 new Random(seed) 固定種子 rnd.nextInt(); // 全範圍 int(含負數) rnd.nextInt(100); // [0, 100) 隨機 int rnd.nextLong(); // 隨機 long rnd.nextBoolean(); // 隨機 boolean rnd.nextFloat(); // [0, 1) float rnd.nextDouble(); // [0, 1) double rnd.nextBytes(bytes); // 填充 byte 數組
new Random(seed) 或 setSeed(seed) 設置種子seedUniquifier() ^ System.nanoTime()(高精度時間 + 原子計數器)// Random.next(int bits) 核心邏輯
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1; // 保留低 48 位
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask; // LCG 公式
} while (!seed.compareAndSet(oldseed, nextseed)); // CAS 原子更新
return (int)(nextseed >>> (48 - bits)); // 取高位
}
X_{n+1} = (a * X_n + c) mod mpublic class PasswordGenerator {
private static final String SPECIAL = "!@#$%^&*_=+-/";
private static final Random RND = new Random(); // 或 ThreadLocalRandom.current()
public static String generate(int length) {
if (length < 4) throw new IllegalArgumentException("長度至少 4 以容納四類字符");
char[] chars = new char[length];
// 1. 保證每類至少一個:隨機選位置填充
chars[randomIndex(chars)] = randomSpecial();
chars[randomIndex(chars)] = randomUpper();
chars[randomIndex(chars)] = randomLower();
chars[randomIndex(chars)] = randomDigit();
// 2. 剩餘位置隨機填充任意類型
for (int i = 0; i < length; i++) {
if (chars[i] == 0) chars[i] = randomAny();
}
return new String(chars);
}
private static int randomIndex(char[] arr) {
int idx;
do { idx = RND.nextInt(arr.length); } while (arr[idx] != 0);
return idx;
}
private static char randomSpecial() { return SPECIAL.charAt(RND.nextInt(SPECIAL.length())); }
private static char randomUpper() { return (char)('A' + RND.nextInt(26)); }
private static char randomLower() { return (char)('a' + RND.nextInt(26)); }
private static char randomDigit() { return (char)('0' + RND.nextInt(10)); }
private static char randomAny() {
switch (RND.nextInt(4)) {
case 0: return randomLower();
case 1: return randomUpper();
case 2: return randomDigit();
default: return randomSpecial();
}
}
}
public static void shuffle(int[] arr) {
Random rnd = new Random();
for (int i = arr.length; i > 1; i--) {
// 從 [0, i) 中隨機選一個,與 i-1 交換
int j = rnd.nextInt(i);
int tmp = arr[i-1];
arr[i-1] = arr[j];
arr[j] = tmp;
}
}
// 測試
int[] cards = new int[52];
for (int i = 0; i < 52; i++) cards[i] = i;
shuffle(cards); // 原地打亂,均勻隨機排列
class WeightedRandom<T> {
private final List<Item<T>> items;
private final double[] cumulative; // 累積概率
private final Random rnd = new Random();
static class Item<T> { T value; int weight; }
public WeightedRandom(List<Item<T>> items) {
this.items = items;
int total = items.stream().mapToInt(i -> i.weight).sum();
cumulative = new double[items.size()];
int sum = 0;
for (int i = 0; i < items.size(); i++) {
sum += items.get(i).weight;
cumulative[i] = (double) sum / total;
}
}
public T next() {
double r = rnd.nextDouble(); // [0, 1)
int idx = Arrays.binarySearch(cumulative, r);
if (idx < 0) idx = -idx - 1; // 插入點即選中索引
return items.get(idx).value;
}
}
// 使用:1元(70%)、5元(20%)、10元(10%)
var rnd = new WeightedRandom<>(List.of(
new Item<>("1元", 70), new Item<>("5元", 20), new Item<>("10元", 10)
));
for (int i = 0; i < 10; i++) System.out.print(rnd.next() + " ");
public class RedPacket {
private int remainMoney; // 分為單位
private int remainCount;
private final Random rnd = new Random();
public RedPacket(int totalCents, int count) {
this.remainMoney = totalCents;
this.remainCount = count;
}
public synchronized int grab() {
if (remainCount <= 0) throw new IllegalStateException("搶光了");
if (remainCount == 1) return remainMoney; // 最後一個全拿
// 平均值的兩倍作為上限,保證期望公平
double max = remainMoney * 2.0 / remainCount;
int money = (int) (rnd.nextDouble() * max);
money = Math.max(1, money); // 最少 1 分
remainMoney -= money;
remainCount--;
return money;
}
}
// 測試:10元 10個紅包
RedPacket rp = new RedPacket(1000, 10);
for (int i = 0; i < 10; i++) System.out.print(rp.grab() + " ");
// 簡化演示:種子 + 編號 → SHA-256 → 取模
public class Lottery {
public static boolean checkWin(String seed, long applicantId, int totalQuota, int totalApplicants) {
String input = seed + ":" + applicantId;
String hash = sha256(input); // 64 位十六進制
long hashVal = Long.parseLong(hash.substring(0, 16), 16); // 取前 64 位
// 簡化:按比例中籤
return (hashVal % totalApplicants) < totalQuota;
}
}
// JDK 7+,每個線程獨立實例,無需同步 ThreadLocalRandom.current().nextInt(100); ThreadLocalRandom.current().nextLong(1000, 2000); // ForkJoinPool、並行流自動使用
SecureRandom sr = new SecureRandom(); byte[] token = new byte[32]; sr.nextBytes(token); // 生成 256-bit 安全 Token // 種子來自 OS 熵池,Linux 為 /dev/urandom,Windows 為 CryptoAPI
ThreadLocalRandomRandom(seed)SecureRandomMath.random()new Random() 在循環中(種子可能相同),不要用偽隨機做加密,不要忽略線程安全隨機數看似簡單,實則蘊含數學、工程與業務的深度權衡。掌握原理與模式,才能在業務中寫出既公平又高效的隨機邏輯。