Java 随机数生成器选型指南:Random、ThreadLocalRandom 与 SecureRandom

在日常开发中,随机数生成是一个再常见不过的需求。但面对 RandomThreadLocalRandomSecureRandom 这些类,你真的选对了吗?

这篇文章不罗列 API,而是从底层原理出发,帮你理清它们之间的本质差异,以及各自的最佳适用场景。


一、Random vs ThreadLocalRandom:并发性能差在哪?

1.1 两者算法完全一致

先明确一点:ThreadLocalRandomRandom 的"线程本地化"版本,它们的随机数生成算法完全一样 ------都是线性同余生成器(LCG)

java

复制代码
// Random 的种子更新
nextseed = (oldseed * 25214903917L + 11L) & ((1L << 48) - 1);

// ThreadLocalRandom 的种子更新(完全一致)
r = (r * 25214903917L + 11L) & ((1L << 48) - 1);

随机质量没有任何区别,差异只在于多线程下的性能表现。

1.2 Random 的性能瓶颈:CAS 自旋与总线锁

Random 通过 CAS(Compare-And-Swap)保证线程安全。所有线程共享一个 AtomicLong 类型的种子,每次生成随机数都要 CAS 更新这个种子。

java

复制代码
// Random.nextInt() 的核心逻辑(简化)
do {
    oldseed = seed.get();
    nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));  // CAS 自旋

问题出在高并发场景

  • 大量线程同时 CAS 更新同一个种子,绝大多数会失败

  • 失败的线程不会阻塞,而是在循环中不断重试(自旋)

  • 每个 CAS 操作在 CPU 层面会触发 LOCK 前缀指令,锁定内存总线,导致所有核心的内存访问串行化

  • 结果:CPU 利用率飙升至 100%,但大部分算力浪费在无意义的重试上,系统吞吐量急剧下降

这就是 CAS 的经典缺陷------适合低竞争,不适合高竞争

1.3 ThreadLocalRandom 的解决方案:空间换时间

ThreadLocalRandom每个线程独立维护一份种子,线程之间完全隔离,不存在任何共享资源。

  • 没有 CAS,没有自旋

  • 没有总线锁

  • 种子更新是普通的 CPU 本地写操作

性能对比数据(来自 JMH 基准测试):

线程数 Random 耗时 (ms) ThreadLocalRandom 耗时 (ms)
50 1192 575
100 4031 162
500 11311 97
1000 23418 174

Random 的性能随线程数增加急剧恶化,而 ThreadLocalRandom 几乎保持恒定。

1.4 一个常见的误区

"每次调用时 new Random(),每个线程都有自己的实例,就不会有竞争了吧?"

这是错的。 即使每个线程持有独立的 Random 对象,每个对象内部的 AtomicLong 种子在 CAS 更新时,仍然会在 CPU 总线层面产生串行化排队。再加上频繁创建对象带来的 GC 压力,高并发下性能依然堪忧。


二、LCG 的先天缺陷:Random 和 ThreadLocalRandom 的通病

LCG(线性同余生成器)是 RandomThreadLocalRandom 共同采用的算法,它有几个无法回避的缺陷:

2.1 周期有限

LCG 的周期最多为 2⁴⁸(Java 实现),生成完这个数量的随机数后序列必然重复。对于大多数业务场景足够了,但科学计算中可能不够。

2.2 低位随机性极差

LCG 的低位比特循环周期极短------最低位只有 2 (0、1、0、1......交替)。这也是 Random.nextInt() 只取高 31 位、跳过最低位的原因。

2.3 可预测性

LCG 是确定性算法,状态空间仅 48 位。攻击者通过连续几个输出值就能反推出种子,进而预测后续所有随机数。

java

复制代码
// 不要用 Random 生成敏感数据
String token = new Random().nextInt(1000000) + "";  // 危险!

2.4 高维分布不均匀

LCG 在三维及以上空间中,所有点会落在少数平行的超平面上,分布极不均匀。这在蒙特卡洛模拟等场景下会导致结果失真。


三、SecureRandom:安全的代价

3.1 它解决了什么问题?

SecureRandom 使用真正的熵源 (如系统中断、硬件随机数发生器、/dev/random)生成种子,输出不可预测,不存在 LCG 的周期性和可预测性问题。

3.2 它的性能问题

SecureRandom 不使用 CAS ,其线程安全依赖 synchronized 同步锁或特定实现的本地策略。

  • 默认实现采用 synchronized,高并发下线程会阻塞等待

  • 某些实现(如 NativePRNG)会读取 /dev/random,可能产生 I/O 阻塞

数据佐证 (OpenJDK 社区测试):8 个线程并发时,NativePRNG 的性能下降至单线程的约 60%。

3.3 高并发下的优化手法

如果既要安全性又要高吞吐量,可以使用 ThreadLocal<SecureRandom> 为每个线程隔离实例,避免锁竞争:

java

复制代码
private static final ThreadLocal<SecureRandom> SECURE_RANDOM_HOLDER = 
    ThreadLocal.withInitial(SecureRandom::new);

四、选型决策树

text

复制代码
你的场景是什么?
│
├── 安全敏感(密码、token、SessionID、加密密钥)
│   └── 必须用 SecureRandom
│
├── 高并发业务(Web 接口、RPC 服务、高吞吐量系统)
│   ├── 对随机质量要求不高(抽奖、随机分配、流量染色)
│   │   └── ThreadLocalRandom(最优)
│   └── 对随机质量要求高(科学计算、蒙特卡洛模拟)
│       └── SplittableRandom(算法更优)或自行 ThreadLocal<SecureRandom>
│
└── 单线程 / 低并发(批处理、管理后台、低频任务)
    └── Random 或 ThreadLocalRandom 都行,Random 更简洁

五、一句话总结

并发性能 随机质量 适用场景
Random 低并发 OK,高并发差 LCG,一般够用 单线程/低并发
ThreadLocalRandom 高并发最优 LCG,一般够用 所有多线程非安全场景
SecureRandom 较差(同步锁/I/O阻塞) 不可预测,安全 密码、token、加密
SplittableRandom 算法优于 LCG 科学计算、并行流

实践建议

  • 一般业务开发 :无脑用 ThreadLocalRandom,简单、高性能、线程安全,覆盖 90% 的场景

  • 安全相关 :必须上 SecureRandom,性能差点可以接受,安全不能妥协

  • 科学计算 :换 SplittableRandom,随机质量更高

选对工具,既不是"无脑用最优",也不是"永远用最安全",而是理解原理后,在性能、安全、代码简洁之间做出合理的取舍


希望这篇文章能帮你彻底理清 Java 随机数生成的选型思路。如果觉得有用,欢迎分享给需要的朋友。

相关推荐
Risehuxyc1 小时前
PHP数据类型
开发语言·php
Wang's Blog1 小时前
Go-Zero基础入门5: 探究go-zero如何基于gRPC扩展客户端
开发语言·后端·golang·go-zero
zh_xuan1 小时前
c++ span的用法
开发语言·c++·span
if_you_can_please_do1 小时前
IDEA插件Maven开发版本管理小助手Maven With Me更新2.9.x版本啦,升版、快速搜索依赖、JDK切换等核心功能助力中大厂业务快速开发与迭代!
java·maven·intellij-idea
caimouse1 小时前
KiSystemStartupBootStack 函数分析
开发语言·reactos
CTA量化套保1 小时前
2026年下半年Python量化入门,先看API如何接进小流程
人工智能·python
zzz_23681 小时前
【Java实习面试算法冲刺】动态规划
java·算法·面试
薛定猫AI1 小时前
【深度解析】动态多智能体团队:并行规划、开发与验证的 Python 实战
大数据·开发语言·python
cfm_29141 小时前
SpringBoot自定义Starter
java·spring boot·后端