优先用ThreadLocalRandom,因其为每线程独享种子、无锁高效;Random仅适用于需可重现序列的场景,如单元测试、固定种子游戏关卡。

多线程环境下优先用 ThreadLocalRandom,单线程或需可重现序列时才用 Random。
为什么 ThreadLocalRandom 在并发场景下更安全
Random 内部用一个共享的 atomicLong(通过 compareAndSet 更新种子),高并发时大量线程争抢同一 CAS 操作,导致自旋重试、吞吐骤降;ThreadLocalRandom 为每个线程维护独立种子和算法状态,完全无锁。
常见错误现象:Random 在压测中 CPU 飙高但吞吐上不去,jstack 显示大量线程卡在 Random.next(int) 的 CAS 循环里。
- 适用场景:Web 请求中生成订单号、验证码、采样阈值等——只要不跨线程复用实例
- 不能直接 new:
ThreadLocalRandom.current()是唯一正确入口,它会自动初始化线程私有实例 - 不支持设置种子(
setSeed(long)),所以无法重现随机序列
Random 唯一不可替代的使用场景
当需要「可重现」的随机行为时,Random 是必须的。比如单元测试中验证概率逻辑、游戏关卡种子固定、算法调试需要稳定输出。
立即学习“Java免费学习笔记(深入)”;
注意:Random 实例本身是线程安全的,但「安全」不等于「高效」——它的同步开销在并发下会成为瓶颈。
- 必须显式传入种子:
new Random(12345L),否则每次运行结果不同 - 不要在多线程里共享一个
Random实例做高频调用(如每毫秒调一次) - 若只是偶尔生成(如应用启动时配置采样率),共享
Random问题不大
API 行为差异:看似一样,实则不能混用
两者方法名高度相似(nextInt()、nextDouble()、nextGaussian()),但实现完全不同:ThreadLocalRandom 不继承 Random,也没有 nextBytes(byte[]) 或 setSeed(long)。
典型误用:ThreadLocalRandom 被当成 Random 的子类注入,编译报错;或试图调用 current().setSeed(…),直接 NoSuchMethodError。
-
ThreadLocalRandom支持区间限定:nextInt(1, 100)(左闭右开),而Random需要手写nextInt(99) + 1 -
Random提供longs()/doubles()流式接口(Java 8+),ThreadLocalRandom也支持,但流是无状态的,不复用内部种子 - 性能差异明显:100 线程并发调用
nextInt(),ThreadLocalRandom吞吐通常是Random的 3–5 倍
别踩这个坑:Spring Bean 中注入 Random 实例
把 Random 声明为 @Bean 并 @Autowired 进多个 Service,在高并发服务中等于主动制造竞争点。
更隐蔽的问题:用 ThreadLocalRandom 但错误地缓存了 current() 返回值(比如赋给 static 字段),会导致后续所有线程都用同一个实例,失去线程隔离性。
- 正确做法:每次需要时调用
ThreadLocalRandom.current().nextInt(),JVM 会确保线程本地性 - 如果真要封装工具类,提供静态方法即可,内部始终调用
current(),不要缓存返回值 - 日志或监控埋点中生成随机 ID,务必用
ThreadLocalRandom,避免拖慢主流程
真正复杂的是混合场景:比如某算法既要可重现又要并发执行。这时得拆开处理——用 Random 派生多个带固定种子的子实例,或改用 SecureRandom(但代价更高)。多数业务代码其实没那么复杂,分清「是否需要重现」和「是否多线程高频调用」两个判断点就够了。










