Java.util.Random 在并发使用时的竞争问题

15

Oracle Java文档表示:

java.util.Random的实例是线程安全的。然而,在跨线程使用同一java.util.Random实例的并发情况下,可能会遇到争用和随之而来的性能不佳。在多线程设计中,考虑改用ThreadLocalRandom。

性能不佳的原因是什么?


1
可能是因为内部状态存在某种同步机制? - Oliver Charlesworth
2个回答

16

在内部,java.util.Random使用AtomicLong保持当前种子,每当请求一个新的随机数时,更新种子会产生竞争。

来自java.util.Random的实现:

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

另一方面,ThreadLocalRandom 通过为每个线程提供一个种子,确保在没有竞争的情况下更新种子。


这也意味着,如果您频繁调用next,则性能不佳只会发生,因为如果更多的线程同时调用nextcompareAndSet将更容易失败。 - TwoThe

3
随机类持有内部状态的同步锁,只有一个线程可以访问它 - 具体来说,它使用了AtomicLong。这意味着,如果您尝试使用多个线程从中读取,只有一个线程可以访问它,导致其他线程等待锁被释放。
ThreadLocalRandom可以代替随机类,提供透明的每个线程实例化,以确保内部状态在每个线程基础上更新,从而避免锁定。
请注意,如果实现正确,则AtomicLong更新操作不应表现得特别差,除非您运行大量线程,因为它可以在JVM内部最优化为类似于x86上的lock xchg的操作。除了锁之外,主要的计算成本可能是长乘和旋转移位的组合。

这不正确。多个线程可以同时访问AtomicLong,没有锁定访问。并发原子使用volatiles。 - Martin Serrano
错误的原因是Random遭受争用的原因是AtomicLong.compareAndSet,如果与许多线程一起使用,它本身也会遭受争用。请参考https://dev59.com/tHA65IYBdhLWcg3w7TWZ。 - Nearoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接