不安全库如何帮助原子变量实现线程安全?

How does Unsafe library help atomic variables to achieve thread safety?

提问人:user2606235 提问时间:1/22/2023 最后编辑:Mark Rotteveeluser2606235 更新时间:1/23/2023 访问量:91

问:

我一直在阅读有关比较和交换 (CAS) 如何在后台工作的网络文章。由于 AtomicInteger、AtomicLong 和其他原子变量都使用了 JNI 的“不安全”库的 compareAndSet() 方法,但是当你进入实际实现时,你会得到一个空白的、类似抽象的方法,它没有告诉你 compareAndSet() 实际上是线程安全的。

@HotSpotIntrinsicCandidate
public final native boolean compareAndSetLong(Object o, long offset,
                                              long expected,
                                              long x);

有没有人想过 JNI 的 Unsafe 库如何保证原子变量的线程安全?

java jvm-hotspot 比较和交换

评论

0赞 Joachim Sauer 1/22/2023
该签名中有两个提示:表示代码不是用 Java 实现的(它不可能),而是在本机 (C++) 代码中实现的。这意味着它甚至不是一个常规的原生方法,而是一个通过深度集成到 JVM 中来实现的方法。native@HotSpotIntrinsicCandidate

答:

2赞 Gilles D. 1/23/2023 #1

请注意,JNI 和 Unsafe 是无关的:Unsafe 不是 JNI 的一部分,正如已经指出的那样,尽管有原生方法,但 Unsafe 不是在 JNI 中实现的。

它在 VM 中直接链接到一些通用的 Atomic::cmpxchg,然后你必须通过一些模板层跟踪这些模板,以找到适当的实现具体取决于你的平台(这里是 linux-amd64)。

1赞 Botje 1/23/2023 #2

实现相当简单。 它由两部分组成:一个是 JNI 方法,它使指令作为字节码解释计算的一部分可用,另一个是内部函数,使指令在 JIT 编译器生成本机代码时可用。

JNI 方法

unsafe.cpp 将实现显示为 JNI 方法

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x)) {
  oop p = JNIHandles::resolve(obj);
  volatile jlong* addr = (volatile jlong*)index_oop_from_field_offset_long(p, offset);
  return Atomic::cmpxchg(addr, e, x) == e;
} UNSAFE_END

Atomic::cmpxchg 调度到执行 CAS 的各种策略,您可以在特定于 OS+CPU 的文件中找到根据汇编指令的实际实现:

template<>
template<typename T>
inline T Atomic::PlatformCmpxchg<4>::operator()(T volatile* dest,
                                                T compare_value,
                                                T exchange_value,
                                                atomic_memory_order /* order */) const {
  STATIC_ASSERT(4 == sizeof(T));
  __asm__ volatile ("lock cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest)
                    : "cc", "memory");
  return exchange_value;
}

还有 1 字节和 8 字节 CAS 的实现,但唯一的区别分别是 和 。cmpxchgbcmpxchgq

JVM 内部函数

正如在对您的问题的评论中所说,该方法还标记了 。(在最近的 JVM 中)。 JVM 按类和方法名称保留所有内部候选项的列表类文件解析器将方法与声明的内部函数相关联。从那里,JIT 编译器可以检测对具有内部函数的方法的调用,并将它们替换为该指令的内联版本或程序集片段。@HotspotIntrinsicCandidate@IntrinsicCandidate