Random在多线程下会卡住,因其共享AtomicLong种子,每次nextInt()需CAS更新,高并发时大量线程自旋重试,CPU空转等待而非生成随机数,导致响应时间突增、吞吐骤降。

为什么 Random 在多线程下会卡住
因为所有线程共用同一个 AtomicLong seed,每次调用 nextInt() 都要通过 CAS 更新种子。高并发时大量线程自旋重试,CPU 花在等锁上,而不是生成随机数。
典型现象是:接口响应时间突增、线程堆栈里频繁出现 compareAndSet 自旋、监控显示 CPU 利用率不高但吞吐骤降——就像你那个秒杀系统里优惠券码生成拖垮整个链路。
-
Random的next()方法里有 do-while 循环 + CAS,失败就重来 - 线程越多,冲突概率越高;100+ 线程争一个 seed,性能断崖式下跌
- 即使只是读取(比如
nextInt(100)),也必须先更新 seed 才能算出结果
ThreadLocalRandom 是怎么绕过竞争的
它不共享 seed,而是把种子直接塞进每个线程自己的 Thread 对象里,用 Unsafe.putLong 写入 threadLocalRandomSeed 字段。这个字段是普通 long,不是原子变量——因为根本不需要跨线程同步。
关键点在于:ThreadLocalRandom.current() 只负责“唤醒”当前线程的本地种子(首次调用才初始化),后续所有 nextInt() 都只读写本线程私有的内存位置。
- 没有 CAS,没有自旋,没有等待
- 每个线程的
nextSeed()直接操作自己线程对象里的字段 - 初始化延迟到第一次使用,避免无谓开销
正确用法和常见误用
别把 ThreadLocalRandom 当成普通工具类去 static 缓存——它本身是单例,但它的状态完全在线程里。错用会导致行为诡异甚至种子复用。
- ✅ 正确:每次需要时调用
ThreadLocalRandom.current().nextInt(100) - ✅ 正确:在工具类里封装为静态方法,内部仍调用
current() - ❌ 错误:声明
private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current();—— 这会把初始线程的 seed 快照固化,其他线程拿到的是未初始化状态或错误种子 - ❌ 错误:在 static 块或类加载时调用
current(),可能触发早于线程准备好的初始化,引发空指针或默认种子污染
示例对比:
// ❌ 危险:static 初始化绑定到类加载线程
private static final ThreadLocalRandom BAD = ThreadLocalRandom.current();
// ✅ 安全:每次按需获取,天然线程隔离
public static int nextInt(int bound) {
return ThreadLocalRandom.current().nextInt(bound);
}
边界情况与隐蔽风险
ThreadLocalRandom 不是万能解药。它快,但不安全;它线程局部,但种子一旦泄露,整条线程的随机序列可被还原。
- 不要用于密码学场景(如 token、salt、密钥派生)——必须用
SecureRandom - 某些老 JDK 版本(如 7u2–7u40)存在
current()初始化竞态,极低概率导致两线程共享种子;建议至少用 7u60+ 或直接切到 8+ - 如果线程是短生命周期(如 Netty 的 EventLoop 线程池),
ThreadLocalRandom的初始化开销几乎可忽略;但如果是频繁创建销毁的线程,要注意threadLocalRandomSeed字段的 GC 友好性(实际影响极小)
最常被忽略的一点:它解决的是「性能竞争」,不是「随机质量」或「安全性」。别因为它快,就把它用在不该用的地方。

















