多线程环境下优先使用ThreadLocalRandom,因其无锁、吞吐量高3–5倍;但不可重现随机序列,且需在业务逻辑中调用current(),禁用于静态初始化。

多线程环境下优先用 ThreadLocalRandom
如果代码运行在多线程场景(比如 Web 服务、定时任务、并行流),Random 会因内部的 AtomicLong 状态更新产生竞争,导致性能下降甚至热点锁问题;ThreadLocalRandom 每个线程持有一份独立实例,无共享状态,吞吐量通常高 3–5 倍。
常见错误现象:ThreadLocalRandom.current().nextInt() 在静态初始化块或类加载早期调用,可能返回 null 或抛 NullPointerException —— 它依赖线程首次调用才初始化。
- 正确做法:总在业务逻辑中调用
ThreadLocalRandom.current(),不要缓存或跨线程传递该实例 - 不适用场景:需要可重现的随机序列(如测试、算法验证),因为
ThreadLocalRandom不支持设置种子 - 兼容性:JDK 7+,但
ints()/longs()/doubles()流式方法仅 JDK 8+
Random 适合单线程或需控制种子的场景
Random 是传统选择,构造时可传入固定 long 种子,保证每次运行生成相同序列,这对单元测试、模拟数据生成、游戏关卡种子等至关重要。
注意:即使在单线程里,若频繁调用(如每毫秒生成数千个数),Random 的 CAS 更新仍比 ThreadLocalRandom 慢;此时可考虑复用同一个 Random 实例,而非反复新建。
立即学习“Java免费学习笔记(深入)”;
- 别写
new Random().nextInt(100)—— 构造开销 + 时间种子碰撞可能导致重复序列 - 要复现结果?必须用
new Random(12345L)这种带种子的方式,无参构造依赖System.nanoTime(),不可控 -
Random的nextGaussian()和nextDouble()精度与算法实现和ThreadLocalRandom一致,无需为精度换库
并行流中别手动用 Random,改用 ThreadLocalRandom 的流方法
写 IntStream.range(0, 1000).parallel().map(i -> new Random().nextInt(100)) 是典型反模式:每个元素新建 Random,种子高度相似,结果分布偏差大,且性能极差。
正确方式是直接用 ThreadLocalRandom 提供的并行友好接口:
ThreadLocalRandom.current()
.ints(0, 100)
.limit(1000)
.parallel()
.forEach(System.out::println);-
ints()/longs()/doubles()返回的是并行友好的IntStream,底层已按线程分片,无需同步 - 这些流方法不接受自定义种子,所以不适合需要可重现性的批量生成
- 若既要种子又要并行,只能自己分段:用固定种子初始化多个
Random实例(如按线程 ID 偏移种子),但复杂度陡增,慎选
别混淆 Math.random() 和前两者
Math.random() 是静态方法,内部其实用的是一个全局 Random 实例(JDK 8 起是 ThreadLocalRandom),但它只提供 [0.0, 1.0) 的 double,功能最弱。
常见误用:用它做整数随机((int)(Math.random() * 100))—— 类型转换截断引入轻微偏差,且无法控制种子;更严重的是,在高并发下它曾是锁竞争点(JDK 7 及以前)。
- 简单脚本或原型代码可容忍,但生产环境请统一替换为
ThreadLocalRandom.current().nextInt(100) - 它不能被 mock,对测试不友好;而
Random可注入、可替换,适合解耦设计 - 没有
Math.random().longs()这种东西,想用流就得切到ThreadLocalRandom
实际选型时,真正卡住人的往往不是“哪个更快”,而是“我能不能接受不可重现”和“这个随机数是不是跑在 ForkJoinPool 里”。一旦涉及并行计算或容器化部署下的线程复用,ThreadLocalRandom 的隔离性就不再是优化项,而是必要项。








