AtomicReference 中的 compareAndSet 和 weakCompareAndSet 有什么区别?

what's the difference between compareAndSet and weakCompareAndSet in AtomicReference?

源码是一样的

public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

public final boolean weakCompareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

有什么意义?

在功能上,两者是相同的。主要区别在于 weakAtomicCompareAndSet 可能会虚假地失败 (see oracle documentation) 并且不提供排序保证

建议使用atomicCompareAndSet,不要使用弱版本

weakCompareAndSet javadoc 是这样解释的:

Atomically sets the value to the given updated value if the current value == the expected value.

May fail spuriously and does not provide ordering guarantees, so is only rarely an appropriate alternative to compareAndSet.

简而言之,javadoc 说 weak 版本是(或曾经是)提供“较弱”保证的版本。

现在,如您所见,这两种方法的当前实现是相同的。根据 Grepcode 站点上的源代码,从 Java 6 到 Java 8(至少)是这样。

所以我推测这两种方法的实现是:

  • 本来是不同的,但由于Unsafe:

    的实现大修而变得相同
    • 为了权宜之计(例如为了节省实施工作
    • 因为“弱”版本的假定性能,或者
    • 因为“弱”版本有问题;例如太难正确使用了。
  • 本来是一样的,由于设计者认为可能会有性能优势,所以指定了差异(但未实现)。

最后的解释不太可能。如果两个方法最初实现相同,则将它们重新实现为不同可能会破坏先前存在的代码。这是一个坏主意,即使对于 Unsafe.


@assylias / @Stefan Gobel 评论了另一种解释。基本上,我们在源代码中看到的“相同代码”实际上可能会被 JIT 编译器重写,从而为两种方法提供不同的机器代码。

这当然是有道理的。 JIT 编译器确实为某些(非本机)方法调用生成特殊情况代码:所谓的“内在函数”。


在 Java 9 中,weakCompareAndSet 方法被标记为已弃用。源码中的解释是:

This method has plain memory effects but the method name implies volatile memory effects (see methods such as {@link #compareAndExchange} and {@link #compareAndSet}). To avoid confusion over plain or volatile memory effects it is recommended that the method {@link #weakCompareAndSetPlain} be used instead.

另一方面,我们现在看到 compareAndSet 现在的实现方式与 weakCompareAndSet / weakCompareAndSetPlain:

不同
public final boolean compareAndSet(V expectedValue, V newValue) {
    return VALUE.compareAndSet(this, expectedValue, newValue);
}

public final boolean weakCompareAndSet(V expectedValue, V newValue) {
    return VALUE.weakCompareAndSetPlain(this, expectedValue, newValue);
}

其中 VALUE 声明为 java.lang.invoke.VarHandle。上面使用的 VarHandle 方法是 native 并标记为内在候选者。

在 x86 上,LOCK CMPXCHG 指令用于实现 CAS。它是原子的,提供(接近)最大排序保证并且不会遭受虚假故障。因此,在 x86 平台上,保证较少的 CAS 没有任何好处。

但是在 PowerPC 或 ARM(没有 LSE 扩展)等其他平台上,CAS 是作为一系列指令实现的,这些指令提供 LL/SC 行为和内存屏障作为单独的构建块。这为您的 CAS 在排序和故障保证方面的强度创造了一些回旋余地。相反,这意味着全强度 CAS 可能是比某些并发算法所需的指令序列成本更高的指令序列。

许多并发算法涉及在 CAS 失败时重试或重新计算操作然后重试的循环。由于 LL/SC 可能会虚假地失败,因此基于它的强大 CAS 实现必须在内部循环。如果代码已经包含一个外循环,它可以通过将强 CAS 替换为允许虚假失败的弱 CAS 来避免内循环。

所以 weakCAS 的存在是为了在弱序架构上允许更高效的代码。

java文档对弱化排序的确切含义含糊不清,因为目前无法用 java 内存模型来表达它。当它与 C++11 内存模型更紧密地对齐时,将来可能会对其进行修改。

JSR-133 Cookbook 的多处理器章节中的 table 概述了平台的不同之处。

同样来自 java 文档,看起来其他答案错过了这个:

The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html#weakCompareAndSet