并发使用 java.util.Random 时的争用

Oracle Java文档说:

java.util.Random 的实例是 threadsafe。但是,跨线程并发使用相同的 java.util.Random 实例可能会遇到争用,从而导致性能下降。请考虑在多线程设计中使用 ThreadLocalRandom。

性能不佳的原因可能是什么?


答案 1

在内部,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 通过每个线程有一个种子来确保种子更新而不会遇到任何争用。


答案 2

随机类在内部状态周围保存一个同步锁,使得只有一个线程可以一次访问它 - 具体来说,它使用 .这意味着,如果您尝试使用多个线程从中读取它,则只有一个线程可以同时访问它,从而导致其他线程等待锁定被释放。AtomicLong

ThreadLocalRandom可以改为使用来提供透明的每线程实例化,以确保内部状态基于每个线程进行更新,从而避免锁定。

请注意,如果实现正确,除非您运行大量线程,否则更新操作不应该执行得很糟糕,因为它基本上可以在JVM中优化到类似于x86。在锁之外,主要的计算成本可能是长乘法和旋转移位的组合。AtomicLonglock xchg


推荐