【问题标题】:what's the difference between compareAndSet and weakCompareAndSet in AtomicReference?AtomicReference 中的 compareAndSet 和 weakCompareAndSet 有什么区别?
【发布时间】:2016-07-25 11:50:32
【问题描述】:

源代码是一样的。

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);
}

有什么意义?

【问题讨论】:

    标签: java atomicreference


    【解决方案1】:

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

    推荐使用 atomicCompareAndSet 代替弱版本

    【讨论】:

    • 但是为什么呢? weakCompareAndSet 可能会虚假失败,但 compareAndSet 不会。它们完全一样。 :(
    • 它们在那个实现中是一样的,是不是每个实现都一样?
    【解决方案2】:

    weakCompareAndSetjavadoc 是这样解释的:

    如果当前值 == 预期值,则自动将值设置为给定的更新值。

    可能会错误地失败并且不提供排序保证,因此很少是 compareAndSet 的合适替代方案。

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

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

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

    • 原本不同,但由于对Unsafe的实施进行了大修而变得相同:

      • 为了权宜之计(例如,为了节省实施工作
      • 因为“弱”版本的假定性能,或者
      • 因为“弱”版本存在问题;例如很难正确使用。
    • 最初是相同的,但指定了不同之处(但未实现),因为设计人员认为可能会有性能优势。

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


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

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


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

    此方法具有普通的记忆效应,但方法名称暗示易失性记忆效应(请参阅 {@link #compareAndExchange} 和 {@link #compareAndSet} 等方法)。为避免混淆普通或易失​​性记忆效应,建议改用 {@link #weakCompareAndSetPlain} 方法。

    另一方面,我们现在看到 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 并标记为内在候选。

    【讨论】:

    • 或更可能:这些方法由 JVM 重新实现(不记得它是如何调用的)。
    • @assylias 内在函数
    • 谢谢。第一个解释确实有道理。
    • 实际读取并发兴趣列表好像weak方法确实是一样的。
    【解决方案3】:

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

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

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

    因此,weakCAS 的存在是为了在弱序架构上实现更高效的代码。

    javadoc 对弱化排序的确切含义含糊不清,因为它目前无法用 java 内存模型表示。这可能会在将来与 C++11 内存模型更紧密地保持一致时进行修改。

    JSR-133 Cookbook 的多处理器章节中的表格概述了平台之间的差异。

    【讨论】:

    • 优秀的答案。
    【解决方案4】:

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

    原子类也支持方法weakCompareAndSet,它有 适用性有限。在某些平台上,弱版本可能更多 在正常情况下比 compareAndSet 有效,但不同之处在于 任何给定的weakCompareAndSet 方法调用都可能返回false 虚假的(也就是说,没有明显的原因)。虚假回报意味着 只有在需要时可以重试该操作,这取决于 保证在变量成立时重复调用 expectedValue 并且没有其他线程也尝试设置 变量最终会成功。 (这种虚假的故障可能为 示例是由于与无关的内存争用效应 预期值和当前值是否相等。)另外 weakCompareAndSet 不提供以下顺序保证 通常需要同步控制。但是,该方法可能是 对更新计数器和统计信息很有用 与程序的其他发生之前的顺序无关。当一个 线程看到由 a 引起的对原子变量的更新 weakCompareAndSet,它不一定看到任何其他的更新 在weakCompareAndSet 之前发生的变量。这可能是 例如,在更新性能统计信息时可以接受,但 否则很少。

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

    【讨论】:

    • 我认为我们完全没有错过这一点。而且我认为这句话并没有真正解决这个问题。问题是问为什么 compareAndSetweakCompareAndSet 实现相同 ...尽管 javadoc(您​​引用的)说它们是不同的?
    猜你喜欢
    • 1970-01-01
    • 2013-04-14
    • 2015-03-24
    • 2011-01-27
    • 2010-10-02
    • 2011-12-12
    • 2010-09-16
    • 2012-03-14
    • 2012-02-06
    相关资源
    最近更新 更多