Java生成随机数主要通过java.util.Random类、Math.random()方法、ThreadLocalRandom和SecureRandom,其中Random类最通用,ThreadLocalRandom适合并发,SecureRandom用于加密场景。
Java产生随机数的几种方法对比
生成随机数在Java开发中几乎处处可见,但不同方法在性能、安全性和使用便利性上差异明显,下面逐一拆解。
java.util.Random类
这是最经典的方式,通过new Random()创建实例,核心方法包括nextInt()、nextDouble()、nextLong()等,构造时可指定种子,例如new Random(seed),种子相同,生成的随机序列就相同,据Java API文档,Random使用48位线性同余算法,多数场景够用,但不宜用于密码学。
- 创建方式:
Random r = new Random(); - 常用方法:
r.nextInt(100)生成0-99的随机整数;r.nextDouble()生成0.0-1.0的double。 - 特点:非线程安全,多线程调用需加锁或使用ThreadLocalRandom。
Math.random()方法
这是最简单的单行方案。Math.random() 内部调用Random类,返回double值在0.0到1.0之间,优点是语法简洁,缺点是无法直接生成整数或指定范围,需要额外运算,例如生成0-99的整数:(int)(Math.random() 100),适合对随机性要求不高的快速原型。
ThreadLocalRandom
Java 7引入,专门用于多线程环境,每个线程维护独立的Random实例,避免了锁竞争,性能优于共享Random,使用方式:ThreadLocalRandom.current().nextInt(100),注意它不能显式设置种子,因此无法复现随机序列,在高并发场景下,行业共识推荐优先使用ThreadLocalRandom。
SecureRandom
当需要加密强随机数时(如生成令牌、密钥),必须使用SecureRandom,它继承自Random,但算法更强(如SHA1PRNG、NativePRNG),创建方式:
SecureRandom sr = new SecureRandom(),性能比Random慢,但安全性高,据Oracle官方文档,SecureRandom会从操作系统熵源获取种子,不可预测。
对比表格
| 方法 | 线程安全 | 性能 | 可设种子 | 加密安全 |
|---|---|---|---|---|
| Random | 否 | 快 | 是 | 否 |
| Math.random() | 是(内部同步) | 中等 | 否 | 否 |
| ThreadLocalRandom | 是 | 最快 | 否 | 否 |
| SecureRandom | 是 | 慢 | 支持扩展 | 是 |
Java随机数范围怎么设置
实际开发中,多数场合需要生成特定范围的随机数,比如游戏中的伤害值、抽奖概率、测试数据,下面给出常见范围的实现代码。
生成0到n的随机整数
使用Random.nextInt(n)直接生成0(含)到n(不含)的整数,例如nextInt(100)返回0-99,如果n是0或负数,会抛出异常,注意nextInt()的边界是左闭右开。
生成指定范围整数(如m到n)
公式:m + random.nextInt(n - m + 1),生成m到n(含两端)的整数,例如生成5到10:5 + random.nextInt(6),也可以用nextInt(n - m + 1) + m,如果使用Math.random(),则需(int)(Math.random() (n - m + 1) + m)。
生成随机浮点数范围
Random.nextDouble()返回0.0-1.0,要映射到m到n:m + random.nextDouble() (n - m),注意结果仍为double,包含m但不包含n,如果需要包含n,可微调公式。
代码示例
Random r = new Random(); // 整数范围 1-100 int num = 1 + r.nextInt(100); // 浮点数范围 0.5-1.5 double d = 0.5 + r.nextDouble() 1.0;
Java随机数种子对结果的影响
种子是随机数生成器的初始状态,相同种子保证每次运行产生相同的随机序列,这在调试和测试中非常有用。
创建Random对象时指定种子
Random r = new Random(42),只要种子是42,后续r.nextInt()每次调用都会返回固定序列,这在复现bug或做A/B测试时很有价值,但注意,如果两次运行之间改变了Random的调用次数,结果会错位。
种子相同的随机序列相同
业内专家指出,在游戏中的“种子地图”或“随机种子世界”就是利用这一特性:只传输种子,双方各自生成相同的地形,Java中,Random的setSeed(long seed)可以重置种子,但这会重新初始化生成器,导致序列从新种子开始。
不指定种子时如何工作
不传种子时,Random构造函数内部使用System.nanoTime()和计数器组合作为种子,确保每次运行结果不同,SecureRandom则从操作系统熵池获取种子,更不可预测。
Java随机数在并发场景下的选择
多线程环境中使用Random很容易出现性能瓶颈或数据错误,下面讨论如何正确选择。
Random的线程安全缺陷
Random的nextInt()等方法内部使用原子变量更新种子,本身是线程安全的,但多个线程竞争同一个Random实例会导致大量CAS失败,性能急剧下降,统计显示,高并发下共享Random的吞吐量可能只有ThreadLocalRandom的十分之一。
ThreadLocalRandom的使用
ThreadLocalRandom.current()返回当前线程的Random实例,无需传参,也不需创建对象,它自动管理每个线程的种子,彻底消除竞争,推荐写法:ThreadLocalRandom.current().nextInt(0, 100),注意它不能跨线程传递,否则会抛出异常。
性能对比示例
在8个线程各生成100万次随机数时,ThreadLocalRandom完成时间约0.2秒,而共享Random需要约2秒,如果使用加锁,时间更长,高并发应用首选ThreadLocalRandom。
实战:生成随机验证码
验证码是随机数在Web开发中的常见应用,下面演示如何生成包含数字和字母的验证码。
生成纯数字验证码
Random r = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(r.nextInt(10));
}
// 输出类似 "483921"
生成字母数字混合验证码
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
SecureRandom sr = new SecureRandom();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(chars.charAt(sr.nextInt(chars.length())));
}
// 输出类似 "A3X9K2"
这里使用SecureRandom是为了防止暴力破解预测,如果只是展示用途,Random也可以。
Java产生随机数常见问题
Java随机数怎么生成一定范围内的随机数?
使用Random.nextInt(n)生成0到n-1的整数,然后通过m + nextInt(n - m + 1)得到m到n的整数,浮点数则用m + nextDouble() (n - m),注意边界是左闭右开,含左不含右。
SecureRandom和Random有什么区别?
Random使用线性同余算法,速度快但可预测,不适合加密,SecureRandom使用更复杂的算法,如SHA1PRNG,并依赖操作系统熵源,产生不可预测的随机数,但性能较低,如果涉及安全令牌、密钥生成,必须用SecureRandom。
如何保证每次运行生成相同的随机数序列?
创建Random对象时指定固定种子,例如new Random(12345),后续调用nextInt()等方法的顺序保持一致,则每次运行结果完全相同,但注意,如果代码中修改了随机数调用次数,序列会偏移。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/545928.html



