跳到主要内容Java 随机数生成器全方位对比 | 极客日志Javajava算法
Java 随机数生成器全方位对比
本文对比了 Java 中多种随机数生成器,包括 java.util.Random、ThreadLocalRandom、SecureRandom、SplittableRandom 及 Java 17+ 的 RandomGenerator。分析了它们的实现原理、线程安全性、性能及适用场景。结论指出:单线程普通场景用 Random,高并发用 ThreadLocalRandom,并行流用 SplittableRandom,安全加密必须用 SecureRandom,科学计算可选 LXM 算法。提供了代码示例与最佳实践建议。
DevStack1 浏览 1. 引言
随机数在软件开发中无处不在:模拟、游戏、加密、负载均衡、测试数据生成等。Java 提供了多种随机数生成工具,从最早的 java.util.Random 到专为并发优化的 ThreadLocalRandom,再到高安全性的 SecureRandom 和适用于并行流的 SplittableRandom。此外,第三方库如 Apache Commons Lang 也提供了增强功能。选择合适的随机数生成器对程序性能、安全性和正确性至关重要。
本文将深入剖析每种随机数生成器的特性、实现原理、优缺点及适用场景,并附有详细的性能对比和最佳实践建议。
2. 随机数的基本概念
- 真随机数:来源于物理现象(如热噪声、量子效应),不可预测,适用于加密、安全令牌。
- 伪随机数:由算法通过种子生成,看似随机但实际确定,可重现。适用于大多数非安全场景。
- 种子(Seed):算法的初始状态,相同种子会产生相同随机序列。
- 周期:伪随机数序列重复之前的长度,长周期更优。
Java 中的随机数生成器都是伪随机的(除了部分 SecureRandom 实现可能使用真随机源),但 SecureRandom 保证了密码学意义上的不可预测性。
3. Java 内置随机数生成器概览
| 类名 | 引入版本 | 线程安全 | 随机性强度 | 性能 | 主要用途 |
|---|
java.util.Random | 1.0 | 是(但竞争激烈) | 中等 | 低并发下较好 | 通用,单线程或低并发 |
java.util.concurrent.ThreadLocalRandom | 1.7 | 是,每个线程独立 | 中等 | 并发极高 | 多线程并发随机数 |
java.security.SecureRandom | 1.1 | 是 | 高(密码级) | 较低 | 加密、安全令牌 |
java.util.SplittableRandom | 1.8 | 否(但专为 fork/join 设计) | 中等 | 极快 | 并行流、ForkJoinPool |
java.util.random.RandomGenerator 及其实现 | 17 | 各异 | 各异 | 各异 | 统一接口,多种新算法 |
3.1 java.util.Random
实现原理:使用 48 位种子,线性同余生成器(LCG),公式:seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1),然后取高位输出。
- 特点:可指定种子,实例化后即可使用。线程安全(通过原子变量更新种子),但高并发下 CAS 竞争严重,性能下降。
- 方法:
nextInt(), nextLong(), nextDouble(), nextGaussian()(生成高斯分布)等。
- 示例:
Random rand = new Random(12345L);
int num = rand.nextInt(10);
- 优点:简单易用,可重现。
- 缺点:并发性能差,随机性不足以用于加密。
3.2 java.util.concurrent.ThreadLocalRandom
实现原理:每个线程持有一个独立的 Random 实例,种子存储在线程本地(Thread 类中的 threadLocalRandomSeed 等字段),避免了共享种子带来的竞争。
- 特点:单例模式,通过
ThreadLocalRandom.current() 获取当前线程的实例。自动设置种子(初始与线程有关),无需显式实例化。
- 方法:与
Random 类似,但增加了 nextInt(int bound) 等直接方法。
- 示例:
int num = ThreadLocalRandom.current().nextInt(1, 100);
- 优点:并发性能极高,适合多线程环境。
- 缺点:无法设置种子(不可重现),不适用于需要确定性序列的场景。
3.3 java.security.SecureRandom
实现原理:依赖底层操作系统提供的熵源(如 /dev/random、/dev/urandom)或硬件随机数生成器,结合密码学哈希算法(如 SHA1PRNG)生成随机数。
- 特点:提供强随机性,满足密码学要求(不可预测)。实现可插拔,通过
getInstance(String algorithm) 指定算法。
- 方法:与
Random 类似,但生成速度慢。
- 示例:
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
- 注意:在 Linux 上,默认使用
/dev/urandom,若需阻塞等待足够熵,可指定 -Djava.security.egd=file:/dev/./urandom。
- 优点:安全,不可预测。
- 缺点:性能较低,可能阻塞。
3.4 java.util.SplittableRandom
实现原理:采用改良的 LCG(使用加法和异或),专为拆分成多个独立实例设计。它的 split() 方法返回一个新的 SplittableRandom 实例,该实例与当前实例独立,但共享相同的算法。
- 特点:非线程安全,但设计用于并行计算,每个线程/任务使用独立的
SplittableRandom 实例。速度极快(比 Random 快 2-3 倍)。
- 方法:与
Random 类似,但缺少 nextGaussian()。
- 示例:
SplittableRandom random = new SplittableRandom();
long stream = random.longs(10, 0, 100);
- 优点:极高性能,适合并行流和 ForkJoin 框架。
- 缺点:不可用于加密,非线程安全(但可通过 split 保证线程独立)。
3.5 Java 17+ 的 RandomGenerator 接口
Java 17 引入 java.util.random.RandomGenerator 接口,统一了随机数生成器的 API,并提供了多种新算法实现,如 "L32X64MixRandom"、"L64X128StarStarRandom" 等(基于 LXM 系列)。这些算法具有长周期、高状态、高性能的特点。
- 获取方式:通过
RandomGenerator.of(String name) 或 RandomGenerator.getDefault()。
- 常见实现:
Random、SecureRandom、SplittableRandom 都实现了该接口。
- 新增算法:
L32X64MixRandom、L64X128StarStarRandom、L128X256MixRandom 等,适用于需要高质量随机数的科学计算。
- 特点:算法多样,可根据需要选择周期和状态大小。
- 示例:
RandomGenerator rng = RandomGenerator.of("L64X128MixRandom");
long val = rng.nextLong();
4. 第三方库随机数生成器
4.1 Apache Commons Lang - RandomUtils 和 RandomStringUtils
Apache Commons Lang 提供了易用的工具类,内部封装了 java.util.Random 实例。
RandomUtils:提供 nextInt、nextLong 等方法,但已废弃,推荐直接使用 JDK 类。
RandomStringUtils:生成随机字符串,如字母数字组合。
String randomStr = RandomStringUtils.randomAlphanumeric(10);
- 注意:内部使用
Random,线程安全但性能一般,且不适用于加密。
4.2 Guava 的随机相关工具
Guava 提供了 com.google.common.hash.Hashing 中的 murmur3_128 等哈希函数,以及 com.google.common.primitives 工具,但没有独立的随机数生成器。对于随机字符串,可以使用 CharMatcher 自行构造。
4.3 其他高性能库
- FastRandom:某些第三方库(如
commons-math)提供更快的随机数生成器,用于科学计算。
- OpenJdk 的
jdk.random 包:在 JDK 17 中已内置 LXM 系列,性能极高,无需第三方。
5. 核心对比维度
5.1 线程安全性
Random:线程安全,通过 CAS 更新种子,但高并发下性能差。
ThreadLocalRandom:线程封闭,无竞争,最佳并发性能。
SecureRandom:线程安全,通常使用同步,但性能开销大。
SplittableRandom:非线程安全,需通过 split() 为每个线程创建新实例。
结论:并发场景首选 ThreadLocalRandom 或 SplittableRandom(并行流)。
5.2 随机质量与周期
Random:周期 2^48,线性同余,低比特位随机性较弱。
ThreadLocalRandom:与 Random 算法相同,但种子维护在线程本地。
SecureRandom:密码学安全,不可预测。
SplittableRandom:周期 2^64,改良算法,分布均匀。
- JDK 17 LXM 系列:周期可达 2^256 或更高,状态大,质量高。
结论:普通应用 Random 足够,加密用 SecureRandom,高质量科学计算用 LXM。
5.3 性能测试(JMH 简要参考)
以下为粗略性能对比(单位:ns/op,生成 10^7 次 int):
| 实现 | 单线程 | 多线程(8 线程) |
|---|
Random | 20 ns | 严重下降,约 500 ns(竞争) |
ThreadLocalRandom | 15 ns | 15 ns(无竞争) |
SecureRandom | 2000 ns | 2000 ns(阻塞严重) |
SplittableRandom | 8 ns | 8 ns(每个线程独立实例) |
L64X128MixRandom (Java 17) | 7 ns | 7 ns(独立实例) |
注:实际值随硬件和 JDK 版本变化,但相对关系稳定。
5.4 可重复性(种子控制)
Random:支持构造时指定种子,方便重现。
ThreadLocalRandom:不支持种子设定。
SecureRandom:支持设置种子,但通常用于增加熵而非重现。
SplittableRandom:支持种子设定,split() 衍生实例基于当前种子。
- LXM 算法:支持种子设定。
结论:需要确定性序列(如单元测试)时用 Random 或 SplittableRandom。
5.5 易用性
Random 最简单,可直接 new Random()。
ThreadLocalRandom 通过静态方法获取,但要注意作用域。
SecureRandom 可能抛出异常,需处理 NoSuchAlgorithmException。
SplittableRandom 与 Random 类似,但缺少高斯分布方法。
6. 特殊随机数需求
6.1 生成随机字符串
- 简单实现:利用
Random 或 ThreadLocalRandom 从字符数组中选取。
- 安全需求:使用
SecureRandom。
- 工具类:
RandomStringUtils(Commons Lang)或自写:
static String randomString(int length, Random random) {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
6.2 生成随机数流
Java 8+ 的 Random、ThreadLocalRandom、SplittableRandom 都支持直接生成流:
Random rand = new Random();
IntStream ints = rand.ints(10, 0, 100);
对于并行流,使用 SplittableRandom 的 splits() 方法可以生成多个独立实例,每个处理一段。
6.3 生成指定分布的随机数
- 高斯分布:
Random.nextGaussian() 生成均值为 0,标准差为 1 的正态分布值。
- 自定义分布:可使用逆变换法或接受 - 拒绝法,或借助第三方库如 Apache Commons Math。
6.4 生成 UUID
Java 内置 UUID.randomUUID() 使用 SecureRandom 生成版本 4 UUID(完全随机)。其性能可能较低,但安全性高。若不需要加密级别,可改用基于 Random 的实现。
7. 深入原理
7.1 线性同余生成器(LCG)
Random 采用 LCG:X_{n+1} = (a * X_n + c) mod m,其中 a=25214903917, c=11, m=2^48。输出时取高比特位(丢弃低 16 位)以改善随机性。LCG 周期短,低比特位周期更短,因此不建议用于蒙特卡洛模拟等需要高质量随机数的场景。
7.2 混合生成器(LXM)
Java 17 引入的 LXM 算法结合了线性同余(LC)和异或移位(XorShift)或混合函数(Mix)。它维护两个状态:一个 LC 状态产生周期性的数值,另一个是加法的 XorShift 状态。最终通过混合函数输出。这种设计具有长周期、高维均匀性,且速度快。
7.3 熵池与阻塞
SecureRandom 在 Linux 上默认读取 /dev/urandom(非阻塞,但初始可能阻塞直到熵池初始化)。若指定 /dev/random,则在熵不足时会阻塞等待。在服务器启动时,若需生成大量安全随机数,建议预先初始化 SecureRandom 或增大熵池(使用 rngd 服务)。
8. 最佳实践与选型建议
8.1 选择指南
- 普通应用(非并发):
new Random() 或指定种子的 Random。
- 多线程并发(无加密需求):
ThreadLocalRandom.current()。
- 并行流/大数据处理:
SplittableRandom 配合 split() 或流 API。
- 加密、令牌、密码学:
SecureRandom(永远不要用 Random 做安全相关)。
- 需要高质量随机数(如模拟)且 JDK 17+:
RandomGenerator.of("L64X128MixRandom")。
- 单元测试需重现:
Random 或 SplittableRandom 固定种子。
8.2 常见陷阱
- 不要重复实例化
Random:每次 new 会消耗熵(其实影响不大,但若循环内 new 会导致性能差且可能因种子相同产生重复序列)。
- 不要在并发中共享同一个
Random:应使用 ThreadLocalRandom 或每个线程独立实例。
- 不要用
Random 生成安全令牌:可被预测。
SecureRandom 可能缓慢:预热或异步初始化,避免在性能关键路径上首次调用。
Math.random() 内部使用 Random:且每次调用都同步,并发性能差,应避免在多线程中使用。
8.3 代码示例:安全令牌生成
public class TokenGenerator {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static String generateToken(int byteLength) {
byte[] bytes = new byte[byteLength];
SECURE_RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
}
8.4 代码示例:并行流中使用 SplittableRandom
long count = new SplittableRandom()
.splits(1000)
.parallel()
.mapToLong(rng -> rng.nextLong(1, 100))
.sum();
9. 未来展望
随着 Java 的发展,随机数生成器将继续演进。目前 RandomGenerator 接口为未来替换算法提供了灵活性。可能的方向包括:
- 支持硬件随机数指令(如 RDSEED)。
- 提供更多可插拔算法,适应不同需求。
- 在虚拟线程环境中,
ThreadLocalRandom 仍然适用,因为虚拟线程也是 Thread 的子类,保留了 thread-local 机制。
10. 总结
| 需求 | 推荐方案 | 备注 |
|---|
| 单线程普通随机 | Random | 简单,可重现 |
| 多线程并发随机 | ThreadLocalRandom | 最高并发性能 |
| 并行流处理 | SplittableRandom | 快速且可拆分 |
| 安全加密 | SecureRandom | 不可预测,性能低 |
| 高质量随机(科学计算) | JDK 17+ LXM 算法 | 长周期,均匀性好 |
| 随机字符串 | RandomStringUtils 或基于 SecureRandom | 依场景定 |
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online