不安全的 compareAndSwapInt 与同步

Unsafe compareAndSwapInt vs synchronize

我发现几乎所有高级同步抽象(如 java.util.concurrent 中的 Semaphore、CountDownLatch、Exchanger)和并发集合都使用 Unsafe 中的方法(如 compareAndSwapInt 方法)来定义临界区。同时我预计同步块或方法将用于此目的。 您能解释一下 Unsafe 方法(我的意思是只有可以自动设置值的方法)比同步更有效吗?为什么会这样?

如果您希望等待很长时间(例如 milli-seconds),使用 synchronised 会更有效率,因为线程可以休眠并释放 CPU 来做其他工作。

如果您希望操作发生得相当快,使用 compareAndSwap 会更有效。这是因为它是一个简单的机器代码指令,只需 10 ns。但是,如果资源非常满足,则该指令必须忙于等待,并且如果它无法获得所需的值,它可以忙碌地消耗 CPU 直到它获得。

如果您使用堆外内存,您可以控制共享数据的布局并避免错误共享(同一缓存行被多个 CPU 更新)。当您有多个可能想要独立更新的值时,这一点很重要。例如用于环形缓冲区。

请注意,典型 JVM(例如热点)的内部实现通常会使用 compare-and-swap 硬件指令作为 synchronized 实现的 部分 如果这样的指令可用(例如 x86),而另一个常见的替代方案是 LL/SC (e.g., POWER, ARM). A typical strategy to for the fast path to use compare-and-swap (or equivalent) to attempt to obtain the lock if it is free, followed possibly by a short spin-loop and finally if that fails falling back to an OS-level blocking primitive (e.g., futex, Events). The details go far beyond this and include techniques such as biased locking 并且最终取决于实现。

上面的回答并不令人满意。方法是:互斥锁(同步)不是必需的,因为只有一个操作完成了工作(互斥锁中要做的所有事情),而且只有一个操作不能中断。但这是一半的答案,因为在多核系统中,另一个 CPU 可以写入相同的内存位置。出于这个原因,compareAndSwap 机器代码指令不仅在缓存中读取和写入,它还读取和写入实际内存。这需要更多的 RAM 访问时间。 CompareAndSwap 机器代码操作检查 RAM 内容与之前读取的值相比是否发生变化,然后才存储新值。如果我有更多时间,我在这里写一个例子。

有效,compareAndSwap 访问比随时锁定和解锁更快。但只有在访问中只需要更改一个确切的内存位置时才能使用它。如果多个内存位置应该经常更改(应该始终一致),则不能使用 compareAndSwap,只能使用 synchronized。在上面的回答中写到,compareAndSwap 经常被用来实现同步操作。这是正确的,因为单个同步(获取互斥量)和结束同步(释放互斥量)只需要一个原子指令,在任务调度程序中。因此原子访问是一切的基础。但是在 synchronized{ .... } 之间,调度程序知道线程切换受到保护。

这种程序方法不仅对 Java 有效,对 C/++(可能还有其他语言 - )也很重要并且可以使用。