【Java零碎知识点】----- java.util.Random 与 Math.random()
一、java.util.Random:面向对象的随机数生成器
1.1 基本用法
Random 是 java.util 包中的一个类,用于生成伪随机数。它提供了丰富的 API,支持多种数据类型的随机值生成。
import java.util.Random;Random rand = new Random();int i = rand.nextInt(); // 任意 int 范围内的整数
int j = rand.nextInt(100); // [0, 100) 的整数(0 到 99)
long l = rand.nextLong(); // 随机 long
float f = rand.nextFloat(); // [0.0f, 1.0f) 的 float
double d = rand.nextDouble(); // [0.0, 1.0) 的 double
boolean b = rand.nextBoolean(); // true 或 false
1.2 种子(Seed)机制
Random 的核心特性之一是可重现性,这依赖于“种子(seed)”机制。
- 构造方法:
new Random():使用当前系统时间(纳秒)作为默认种子。new Random(long seed):使用指定的长整型值作为种子。
Random r1 = new Random(123);
Random r2 = new Random(123);System.out.println(r1.nextInt()); // 例如:-1193959466
System.out.println(r2.nextInt()); // 同样:-1193959466
关键点:只要种子相同,
Random实例生成的随机数序列就完全一致。这是“伪随机数生成器(PRNG)”的本质——确定性算法 + 初始状态 = 可预测序列。
这一特性在单元测试、算法调试、游戏关卡复现等场景中极为有用。
1.3 内部实现简析
Random 内部使用 线性同余生成器(Linear Congruential Generator, LCG) 算法(具体为 java.util.Random 使用的是 48 位 LCG 变种)。
- 每次调用
nextInt()等方法时,内部状态(一个 48 位的种子)会按固定公式更新。 - 输出值由该状态的高位或低位截取而来。
- 算法简单、速度快,但周期有限(约 2^48),且不具备密码学安全性。
1.4 线程安全性
Random 不是线程安全的。
- 多个线程共享同一个
Random实例时,内部状态可能被并发修改,导致:- 生成重复值;
- 序列混乱;
- 极端情况下状态损坏。
虽然某些 JVM 实现中
nextInt()等方法加了synchronized(如早期 HotSpot),但官方文档明确指出:不应依赖其线程安全性。
正确做法:
- 每个线程使用自己的
Random实例; - 或改用
ThreadLocalRandom(但本文聚焦于Random和Math.random())。
1.5 性能特点
- 初始化快;
- 单次调用开销极低(纳秒级);
- 适合高频、单线程场景。
二、Math.random():静态便捷方法
2.1 基本用法
Math.random() 是 java.lang.Math 类中的一个静态方法,返回一个 double 类型的值:
double d = Math.random(); // 返回 [0.0, 1.0) 范围内的 double
若需生成整数,需手动转换:
// 生成 [0, 100) 的整数
int num = (int) (Math.random() * 100);// 生成 [1, 10] 的整数
int dice = (int) (Math.random() * 10) + 1;
2.2 内部实现机制
很多人误以为 Math.random() 是“原生”的随机函数,其实它底层完全依赖 java.util.Random。
查看 JDK 源码(以 OpenJDK 为例):
private static final class RandomNumberGeneratorHolder {static final Random randomNumberGenerator = new Random();
}public static double random() {return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
Math.random()内部持有一个 静态的、单例的Random实例;- 首次调用时初始化该实例;
- 后续所有调用都复用这个实例。
三、对比总结:Random vs Math.random()
| 特性 | java.util.Random | Math.random() |
|---|---|---|
| 返回类型 | 支持 int、long、float、double、boolean | 仅 double |
| 指定范围 | 直接支持(如 nextInt(100)) | 需手动计算((int)(Math.random()*100)) |
| 种子控制 | ✅ 支持(可重现序列) | ❌ 不支持 |
| 线程安全 | ❌ 非线程安全 | ✅ 线程安全(内部同步) |
| 性能(单线程) | ⚡ 极快 | 快(首次有初始化开销) |
| 性能(多线程) | 快(若每个线程独立实例) | ❌ 慢(锁竞争) |
| 使用复杂度 | 稍高(需创建对象) | 极简(直接调用) |
| 适用场景 | 通用、测试、需要多种类型或种子控制 | 简单脚本、教学、低频单线程随机 |
