CAS 是一个像旋转一样的循环吗?

Is CAS a loop like spin?

本文关键字:一样 循环 一个 CAS 旋转      更新时间:2023-10-16

我在阅读sun.misc.Unsafe.Java的代码时遇到了一个问题。

CAS 是一个像旋转一样的循环吗?

起初,我认为CAS只是一种低寿命的原子操作。但是,当我尝试查找函数compareAndSwapInt的源代码时,我发现 cpp 代码如下所示:

jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == 1, "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
new_val_as_bytes[offset] = exchange_value;
while (cur_as_bytes[offset] == compare_value) {
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
return cur_as_bytes[offset];
}

我在这个原子函数中看到了"何时"和"中断"。

是旋转方式吗?

相关代码链接:

http://hg.openjdk.java.net/jdk8u/jdk8u20/hotspot/file/190899198332/src/share/vm/prims/unsafe.cpp

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/07011844584f/src/share/classes/sun/misc/Unsafe.java

http://hg.openjdk.java.net/jdk8u/jdk8u20/hotspot/file/55fb97c4c58d/src/share/vm/runtime/atomic.cpp

CAS

是一个返回值10单个操作,这意味着此操作是否已成功,因为您正在执行您希望此操作成功的compareAndSwapInt,因此操作会重复直到它起作用。

我认为您还将此与spin lock混淆,这基本上意味着在此值为"1"时执行某些操作(例如);所有其他线程等到该值为零(通过compareAndSwap),这实际上意味着某个线程已完成工作并释放了锁(这称为release/acquire语义)

CAS 操作不是旋转,而是硬件级别的原子操作。在 x86 和 SPARC 处理器上,CAS 只有一条指令,它支持intlong操作数。

实际上,Atomic::cmpxchgint/long重载是使用单个cmpxchgl/cmpxchgq指令在x86上生成的。

您看到的是一个Atomic::cmpxchgbyte重载,它绕过了 CAS 指令在byte级别模拟 CAS 的限制。它通过对与byte位于同一地址的int执行CAS来实现,然后只检查其中的一个byte,如果CAS由于其他3个字节的更改而失败,则重复。比较和交换仍然是原子的,只是有时需要重试,因为它覆盖的字节数超过了必要的字节数。

CAS通常是一个硬件指令,就像整数加法或比较一样(只是速度较慢)。指令本身可以分解为所谓的微码的几个步骤,并且可能确实包含低级循环或对另一个处理器组件的阻塞等待。但是,这些是处理器体系结构的实现细节。还记得CS中的任何问题都可以通过添加另一层间接来解决的说法吗?这也适用于这里。Java 中的原子操作实际上可能涉及以下层:

  1. Java 方法签名。
  2. 一个 C(++) JNI 方法来实现它。
  3. C(++)"编译器内在的",如GCC的__atomic_compare_exchange
  4. 实际的处理器指令。
  5. 实现此指令的微码。
  6. 所述微码所使用的附加层,例如缓存一致性协议等。

我的建议是不要担心所有这些工作原理,除非任何一种情况都适用:

  • 由于某种原因,它不起作用。这可能是由于平台错误。
  • 它太慢了。

单元测试可以帮助您识别前一种情况。基准测试可以帮助您识别后一种情况。但应该指出的是,如果 Java 提供给您的 CAS 很慢,那么您可能无法自己编写更快的 CAS。因此,在这种情况下,最好的办法是更改数据结构或数据流,例如进一步减少所需的线程同步量。