【问题标题】:Java volatile reference vs. AtomicReferenceJava volatile 引用与 AtomicReference
【发布时间】:2010-09-21 19:43:01
【问题描述】:

如果我只使用来自AtomicReferenceget()set() 方法,volatile 对象引用和AtomicReference 之间有什么区别吗?

【问题讨论】:

    标签: java concurrency


    【解决方案1】:

    简短的回答是:不。

    来自java.util.concurrent.atomic 包文档。引用:

    atomics 的访问和更新的记忆效应一般遵循 volatile 的规则:

    • get 具有读取 volatile 变量的记忆效应。
    • set 具有写入(分配)volatile 变量的记忆效应。

    顺便说一句,那个文档非常好,所有的东西都解释了。


    AtomicReference::lazySet 是一种较新的 (Java 6+) 操作,其语义无法通过 volatile 变量实现。请参阅this post 了解更多信息。

    【讨论】:

    【解决方案2】:

    不,没有。

    AtomicReference 提供的额外功能是 compareAndSet() 方法和朋友。如果您不需要这些方法,可变引用提供与 AtomicReference.set() 和 .get() 相同的语义。

    【讨论】:

      【解决方案3】:

      有几个区别和权衡:

      1. 使用 AtomicReference get/set 具有与 volatile 字段相同的 JMM 语义(如 javadoc 所述),但 AtomicReference 是引用的包装器,因此任何对该字段的访问都涉及进一步的指针追逐

      2. 内存占用成倍增加(假设是压缩的 OOP 环境,大多数 VM 都是如此):

        • 易失性参考 = 4b
        • AtomicReference = 4b + 16b (12b object header + 4b ref field)
      3. AtomicReference 提供了比 volatile 引用更丰富的 API。您可以使用AtomicFieldUpdater 或使用Java 9 的VarHandle 重新获得易失性引用的API。如果您喜欢用剪刀跑步,也可以直接联系sun.misc.UnsafeAtomicReference 本身是使用Unsafe 实现的。

      那么,什么时候最好选择一个而不是另一个:

      • 只需要获取/设置?坚持使用易变字段、最简单的解决方案和最低的开销。
      • 需要额外的功能吗?如果这是代码的性能(速度/内存开销)敏感部分,请在 AtomicReference/AtomicFieldUpdater/Unsafe 之间进行选择,您往往会为提高性能付出可读性和风险。如果这不是敏感区域,请选择AtomicReference。库编写者通常会根据目标 JDK、预期的 API 限制、内存限制等混合使用这些方法。

      【讨论】:

        【解决方案4】:

        JDK source code 是解决此类困惑的最佳方法之一。如果您查看 AtomicReference 中的代码,它使用 volatie 变量进行对象存储。

        private volatile V value;
        

        所以,很明显,如果您打算在 AtomicReference 上使用 get() 和 set(),那就像使用 volatile 变量一样。但正如其他读者评论的那样,AtomicReference 提供了额外的 CAS 语义。因此,首先决定是否需要 CAS 语义,如果只需要,则使用 AtomicReference。

        【讨论】:

        • “JDK 源代码是解决此类困惑的最佳方法之一” => 我不一定同意 - javadoc(这是类的合同) 是最好的方法。您在代码中找到的内容回答了特定实现的问题,但代码可以更改。
        • 例如,hashmap 中的this variable 在 JDK 6 中是易失性的,但在 Java 7 中不再是易失性的。您的代码是否基于变量易失性这一事实,在升级时它会损坏你的 JDK ......诚然,这个例子是不同的,但你明白了。
        • 那是 CAS 的一些标准缩写吗?
        • 比较和交换 =)
        【解决方案5】:

        AtomicReference 提供了普通 volatile 变量不提供的附加功能。当您阅读 API Javadoc 时,您会知道这一点,但它还提供了一个锁,对某些操作很有用。

        但是,除非您需要此附加功能,否则我建议您使用普通的 volatile 字段。

        【讨论】:

        • 所以不同之处在于它们的性能。如果没有区别,您永远不会建议使用其中一个。
        • 性能差不多。 AtomicRefrence 增加了复杂性和内存使用。
        • @BT volatile 字段可以像任何常规字段一样使用,而访问 AtomicReference 中的值需要通过 getset 方法。
        【解决方案6】:

        有时即使您只使用gets 和sets,AtomicReference 也可能是一个不错的选择:

        易失性示例:

        private volatile Status status;
        ...
        public setNewStatus(Status newStatus){
          status = newStatus;
        }
        
        public void doSomethingConditionally() {
          if(status.isOk()){
            System.out.println("Status is ok: " + status); // here status might not be OK anymore because in the meantime some called setNewStatus(). setNewStatus should be synchronized
          }
        }
        

        使用 AtomicReference 的实现将免费为您提供写时复制同步。

        private AtomicReference<Status> statusWrapper;
        ...
        
        public void doSomethingConditionally() {
          Status status = statusWrapper.get();
          if(status.isOk()){
            System.out.println("Status is ok: " + status); // here even if in the meantime some called setNewStatus() we're still referring to the old one
          }
        }
        

        有人可能会说,如果您替换,您仍然可以获得正确的副本:

        Status status = statusWrapper.get();
        

        与:

        Status statusCopy = status;
        

        但是第二个更有可能在将来“代码清理”期间被某人意外删除。

        【讨论】:

          猜你喜欢
          • 2014-02-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-27
          • 1970-01-01
          • 2019-01-23
          • 1970-01-01
          相关资源
          最近更新 更多