"多线程别共享一个Random实例,用ThreadLocalRandom"是常见建议——这篇真实用8线程各调用500万次nextInt()做压测,量化这个建议背后真实的性能差距有多大。
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时间浪费在无意义的自旋竞争上,而不是真正的随机数计算。
ThreadLocalRandomDemo.java ThreadLocalRandom r1 = ThreadLocalRandom.current(); ThreadLocalRandom r2 = ThreadLocalRandom.current(); // 同一线程内再调用一次
真实同一线程内 ThreadLocalRandom.current() == ThreadLocalRandom.current(): true 真实3个不同线程各自nextInt(1000000)结果: 632137, 575694, 434278
每一行都是真实运行这段代码得到的结果,包含一个反直觉的发现。