多线程环境下应使用ThreadLocalRandom替代Random,因其无锁、线程隔离、性能高;单线程场景Random更合适,支持自定义种子和完整API;Math.random()仅适用于简单原型。

多线程环境下直接用 Random 会出问题
并发场景下,多个线程共用同一个 Random 实例时,nextLong()、nextInt() 等方法内部会竞争同一把锁(AtomicLong 或同步块),导致吞吐量下降,甚至出现明显性能瓶颈。这不是“偶尔慢”,而是在高并发压测中能观测到的 RT 上升和 CPU 软中断升高。
- 现象:线程数增加后,随机数生成速率不增反降,
jstack可见大量线程阻塞在Random.next()内部 - 根本原因:所有实例共享一个原子状态更新逻辑,无法真正并行
- 误区:有人以为“自己加锁保护
Random”就能解决——这只会让问题更重,且没解决本质竞争
ThreadLocalRandom 是专为并发设计的替代方案
ThreadLocalRandom 不是 Random 的子类,也不共享状态;它利用 ThreadLocal 为每个线程维护独立种子和计算逻辑,调用开销极低,无锁,且初始化延迟到首次使用。
- 必须通过
ThreadLocalRandom.current()获取实例,不能 new —— 否则抛UnsupportedOperationException - 不支持设置自定义种子(
setSeed()被禁用),因为种子由线程本地机制隐式管理 - 只提供
nextInt()、nextLong()、nextDouble()等常用方法,没有nextGaussian()这类开销大的方法 - 在 ForkJoinPool 或虚拟线程(Loom)中同样适用,JDK 19+ 已验证其与
ScopedValue兼容
单线程或低频场景,Random 更简单可控
如果只是单元测试、配置加载、命令行工具等单线程上下文,Random 反而是更合适的选择:可预测(能传入固定 seed)、方法更全(比如 nextGaussian()、ints() 流式接口)、调试友好。
- 需要复现某次随机行为?
new Random(12345)比任何ThreadLocalRandom都可靠 - 要生成服从正态分布的值?
Random.nextGaussian()是标准解,ThreadLocalRandom不提供 - 用在 Spring Bean 中被多个单线程组件引用?只要不跨线程共享实例,
Random完全安全
别混淆 Math.random() 和前两者
Math.random() 底层其实调用的是静态的 ThreadLocalRandom 实例,但它只返回 [0.0, 1.0) 的 double,且无法控制种子或分布类型。它适合快速原型,但不适合对随机性有明确要求的业务逻辑。
立即学习“Java免费学习笔记(深入)”;
- 调用
Math.random()一百万次,比直接用ThreadLocalRandom.current().nextDouble()多一次方法查表和范围缩放 - 它和
ThreadLocalRandom共享同一套线程本地状态,所以不会额外增加线程局部存储压力 - 但如果你需要整数、带边界的随机值(如
nextInt(100)),硬用Math.random()做转换反而易出边界错误
Random,但你自己的服务代码若在高并发路径上反复 new Random()(没复用),也会因构造开销和内部 SecureRandom 探测逻辑拖慢性能——这时候不是换不换的问题,而是该统一用 ThreadLocalRandom.current() 并避免重复获取。








