如果没有实现您想要的原子操作的内置语言,请仅在您的源代码中编写 CAS 重试循环。 硬件(尤其是 x86)通常可以做得更好。
Java 的AtomicInteger
hasgetAndIncrement()
和incrementAndGet()
方法(至少从 Java 7 开始)使得 JVM 可以轻松地将其 JIT 到 asm 中,这比实际的 CAS 重试循环更有效。这就像 C++11 的std::atomic::fetch_add()
. 另请参阅AtomicInteger 的实际用途。
在 x86 上,您希望您的 JVM 能够利用 x86 对这个操作的硬件支持。 如果您使用直接映射到它的函数,而不是优化器必须努力优化为非循环实现的 CAS 重试循环,则更有可能发生这种情况。
(当多个 CPU 内核竞争同一个缓存行时,ed 操作存在硬件总线/缓存仲裁lock
;一次只有一个线程可以实际拥有缓存行并进行增量。您可以争辩说它是无等待的,即使“步骤”是时钟周期而不是 CPU 指令:lock
即使所有其他内核都在同一高速缓存行上敲击,ed 操作在任何给定系统上等待多长时间可能都有一个较低的上限。)
; possible x86 implementation of incrementAndGet() for a 32-bit integer
; which you'd hopefully get (after inlining and so on)
mov eax,1
lock xadd [mem], eax ; atomically do [mem]+=eax, and put the old value in eax
inc eax ; old_value += 1 to get the new value
; result in EAX
不需要循环。
在 LL/SC 机器(大多数非 x86,如 ARM、PowerPC、MIPS)上,会有一个重试循环,但它不完全是 CAS。LL/SC 机器上的 CAS 重试循环有额外的开销。很轻微,但是让JVM直接看到你想要的原子操作肯定更好。有关CAS 与 LL/SC 的更多讨论,请参阅原子清除无符号整数的最低非零位。CAS 循环理论上可以优化为纯 LL/SC 循环。
该问题也是您最好的选择(在 C++ 或 Java 源代码中)是 CAS 重试循环的示例,因为该语言没有执行您想要的操作的原子原语。(也没有任何常见的硬件)。