Java 易失性引用与 AtomicReference

如果我只是使用 get()set()-方法,那么 volatile对象引用和 AtomicReference之间有什么区别吗?

46277 次浏览

简短的回答是: 不。

引自 java.util.concurrent.atomic软件包文档:

对原子的访问和更新的内存效果通常遵循针对 volals 的规则:

  • get具有读取 volatile变量的内存效果。
  • set具有写入(分配) volatile变量的内存效果。

顺便说一下,这个文档非常好,并且解释了一切。


AtomicReference::lazySet 是一个较新的(Java6 +)操作,其语义无法通过 volatile变量实现。

不,没有。

AtomicReference 提供的附加功能是 compareAndSet ()方法和好友。如果您不需要这些方法,则可以使用与 AtomicReference.set ()和。得到()。

AtomicReference 提供了普通 Volatile变量所不能提供的额外功能。读过 API Javadoc 之后,您就会知道这一点,但是它也提供了一个对某些操作有用的锁。

但是,除非您需要这个附加功能,否则我建议您使用一个简单的 volatile字段。

JDK 源代码 是解决这种困惑的最好方法之一。如果您查看 AtomicReference 中的代码,就会发现它使用了一个用于对象存储的挥发变量。

private volatile V value;

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

这里有几个不同之处和权衡:

  1. 使用 AtomicReference get/set 的 JMM 语义学作为易失性字段(作为 javadoc 状态)是相同的,但是 AtomicReference是引用的包装器,因此 任何对字段的访问都涉及到进一步的指针追踪

  2. 内存占用成倍增加(假设一个压缩的 OOP 环境,这对于大多数虚拟机来说都是正确的) :

    • 易挥发参考值 = 4b
    • AtomicReference = 4b + 16b (12b 对象头 + 4b 参考字段)
  3. AtomicReference提供了比易失性引用更丰富的 API。您可以通过使用 AtomicFieldUpdater或使用 Java9的 VarHandle来重新获得可变引用的 API。如果你喜欢用剪刀跑步,你也可以直接伸手去拿 sun.misc.UnsafeAtomicReference本身是使用 Unsafe实现的。

那么,什么时候选择一个比另一个好呢:

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

有时候,即使只使用 gets 和 set,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;

然而,第二个更有可能在将来的“代码清理”中被某人意外地删除。