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


Oracle Java文档 说:

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

可能是表现不佳的原因是什么?


4214
2018-03-10 23:32


起源

大概是因为内部状态有某种同步? - Oliver Charlesworth


答案:


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


13
2018-03-10 23:36



这也意味着只有在打电话时才会出现性能不佳的情况 next 非常频繁 compareAndSet 如果有更多线程调用,则会更频繁地失败 next与此同时。 - TwoThe


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

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

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


3
2018-03-10 23:40



这不对。多个线程可以同时访问AtomicLong。访问没有锁定。并发原子使用挥发物。 - Martin Serrano
不正确,Random遭受争用的原因是 AtomicLong.compareAndSet,如果与许多线程一起使用,它本身会受到争用。参考 stackoverflow.com/questions/3556283/... - Nearoo