关于多线程:并发使用java.util.Random时的争用

Contention in concurrent use of java.util.Random

Oracle Java Documentation says:

Instances of java.util.Random are threadsafe. However, the concurrent use of the same java.util.Random instance across threads may encounter contention and consequent poor performance. Consider instead using ThreadLocalRandom in multithreaded designs.

穷人表现背后的原因是什么?


在内部,java.util.random使用当前种子保持原子长度,并且每当请求一个新的随机数时,在更新种子时就会发生争用。

从java.util.random的实现中:

1
2
3
4
5
6
7
8
9
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通过每个线程有一个种子来确保在不面临任何争用的情况下更新种子。


随机类围绕内部状态持有一个同步锁,这样只有一个线程可以一次访问它——具体来说,它使用AtomicLong。这意味着,如果试图用多个线程从中读取,那么只有一个线程可以同时访问它,从而导致其他线程等待直到释放锁。

可以使用ThreadLocalRandom来提供每个线程实例的透明性,以确保在每个线程的基础上更新内部状态,从而避免锁定。

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