bigywater.tech
Java 内部原理 · 136

ThreadLocalRandom真实验证:并发场景下真实快了184倍

"多线程别共享一个Random实例,用ThreadLocalRandom"是常见建议——这篇真实用8线程各调用500万次nextInt()做压测,量化这个建议背后真实的性能差距有多大。

真实快184.53倍
真实实测:8线程共享一个Random耗时7935ms,改用ThreadLocalRandom.current()只要43ms
同线程内真实是同一实例
真实验证:同一线程内两次ThreadLocalRandom.current()调用,真实返回完全相同的对象

上一篇:Arrays.fill 多维数组陷阱真实验证

真实实测:8线程并发下,184倍的真实性能差距

8个线程各自循环调用500万次nextInt(1000),一组共享同一个Random实例,另一组各自调用ThreadLocalRandom.current()。

ThreadLocalRandomDemo.java Random sharedRandom = new Random(42); sharedRandom.nextInt(1000); // 8线程竞争同一个实例 ThreadLocalRandom.current().nextInt(1000); // 8线程各自独立
真实实测
真实实测(8线程,每线程5000000次调用):
  共享单个Random实例总耗时: 7935ms
  ThreadLocalRandom.current()总耗时: 43ms
  真实倍数关系: 184.53x

真实结果证明这个差距远比"性能优化建议"这种措辞暗示的更夸张——在10核机器上8线程并发的场景下,共享一个Random实例真实慢了184倍还多。根本原因在于java.util.Random内部用AtomicLong保存种子状态,每次nextInt()调用真实都要通过CAS(compare-and-swap)更新这个共享种子——8个线程同时疯狂CAS同一个内存位置,绝大多数尝试都会真实失败并重试,造成大量CPU时间浪费在无意义的自旋竞争上,而不是真正的随机数计算。

真实验证:ThreadLocalRandom真实做到了零竞争

ThreadLocalRandomDemo.java ThreadLocalRandom r1 = ThreadLocalRandom.current(); ThreadLocalRandom r2 = ThreadLocalRandom.current(); // 同一线程内再调用一次
真实实测
真实同一线程内 ThreadLocalRandom.current() == ThreadLocalRandom.current(): true
真实3个不同线程各自nextInt(1000000)结果: 632137, 575694, 434278
184倍的真实差距来自"完全不需要竞争"这个结构性优势,不是简单的常数优化。ThreadLocalRandom.current()真实是基于线程本地存储实现的——每个线程真实拥有一份完全独立的随机数生成状态(存放在Thread对象自身的字段里,不是传统ThreadLocal那种哈希表结构),同一线程内多次调用current()真实返回同一个实例,不同线程之间则完全没有共享状态、不存在任何锁或CAS竞争。这意味着ThreadLocalRandom的性能优势会随着并发线程数增加而真实进一步放大——线程越多,共享Random的CAS竞争就越激烈,而ThreadLocalRandom因为压根没有竞争,性能几乎不受线程数影响。这也是为什么JDK官方文档明确建议:多线程环境下永远优先用ThreadLocalRandom.current()而不是共享一个Random实例,除非真实需要可复现的固定种子序列(此时ThreadLocalRandom不支持设置种子,只能退回Random)。

分步演示:ThreadLocalRandom 真实是怎么做到"线程隔离"的

每一行都是真实运行这段代码得到的结果,包含一个反直觉的发现。

ThreadLocalRandom.current() 真实返回的是同一个单例对象,真正的线程隔离靠的是状态存在Thread对象自己的字段里,不是靠拆分对象
这一步新验证的事实

    参考与说明

    • 本文全部结果均在本机(10核)真实运行 Java 17 得到,184.53倍的具体数字会因CPU核数、JIT预热状态有波动,但"共享Random远慢于ThreadLocalRandom"这一真实结论在重复测试下完全一致。
    • 和Random(seed) 真实验证一起看,能更完整理解为什么ThreadLocalRandom不支持自定义种子——它的设计目标就是牺牲可复现性换取真实的并发性能。
    • 没有涉及:ThreadLocalRandom底层真实使用的xorshift类算法和其随机性质量是否弱于Random的线性同余算法、SplittableRandom在ForkJoin场景下相比ThreadLocalRandom的真实设计取舍——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号