并发使用java.util.Random时的争用
Oracle Java文档说:
java.util.Random的实例是线程安全的。 但是,跨线程并发使用相同的java.util.Random实例可能会遇到争用,从而导致性能不佳。 请考虑在multithreading设计中使用ThreadLocalRandom。
可能是表现不佳的原因是什么?
在内部,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确保通过每个线程拥有一个种子来更新种子而不会遇到任何争用。
random类围绕内部状态保持同步锁,这样只有一个线程可以同时访问它 – 具体来说,它使用AtomicLong
。 这意味着如果您尝试使用多个线程从中读取,则只有一个线程可以一次访问它,导致其他线程等待直到锁定被释放。
可以使用ThreadLocalRandom
来提供透明的每线程实例化,以确保在每个线程的基础上更新内部状态,从而避免锁定。
请注意,如果正确实现,除非您运行大量线程,否则AtomicLong
更新操作不应该执行得非常糟糕,因为它基本上可以在JVM中优化到像x86上的lock xchg
。 锁定之外的主要计算成本可能是长乘法和旋转位移的组合。