Java中的volatile引用与AtomicReference有何区别?

151

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

6个回答

126

简短回答是:不可以。

根据java.util.concurrent.atomic包的文档,引用如下:

对于原子访问和更新的内存效果通常遵循volatile变量的规则:

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

顺便说一句,这个文档非常好,所有内容都有解释。


AtomicReference::lazySet是一个更新的(Java 6+)操作,它具有通过volatile变量无法实现的语义。请参考此帖子获取更多信息。


16
更详细的回答会是什么? - Julien Grenier
同意。我们至少需要一个链接。 - Julien Chastang
2
更详细的答案链接: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/atomic/package-summary.html - Alex Siman
那么,如果不像getAndSet()一样使用原子性,写一个volatile是什么意思? - user1767316

53

没有。

AtomicReference 提供的额外功能是 compareAndSet() 方法及其相关方法。如果您不需要这些方法,一个 volatile reference 可以提供与 AtomicReference.set() 和 .get() 相同的语义。


33

有几个不同点和权衡:

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

  2. 内存占用增加(假设为压缩 OOPs 环境,这对大多数 VM 都是正确的):

  • volatile ref = 4b
  • AtomicReference = 4b + 16b(12b 对象头 + 4b 引用字段)
  1. AtomicReference 提供了比 volatile 引用更丰富的 API。您可以通过使用 AtomicFieldUpdater 或 Java 9 中的 VarHandle 来恢复 volatile 引用的 API。如果您喜欢运行剪刀,也可以直接使用 sun.misc.UnsafeAtomicReference 本身是使用 Unsafe 实现的。

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

  • 只需要get/set?坚持使用volatile字段,这是最简单的解决方案,开销最小。
  • 需要额外的功能?如果这是您代码中性能(速度/内存开销)敏感的部分,请在AtomicReference/AtomicFieldUpdater/Unsafe之间做出选择,其中您往往会为了性能而付出可读性和风险。如果这不是敏感区域,只需选择AtomicReference即可。库编写者通常根据目标JDK、预期API限制、内存限制等使用这些方法的组合。

8
JDK源代码是解决这类问题的最佳途径之一。如果您查看AtomicReference中的代码,它使用了一个volatile变量来存储对象。请参考JDK源代码
private volatile V value;

显然,如果你只是使用AtomicReference的get()和set()方法,那就像使用一个volatile变量。但正如其他读者所评论的,AtomicReference提供了额外的CAS语义。因此,首先确定是否需要CAS语义,只有在需要时才使用AtomicReference。


17
“JDK源代码是解决这类困惑的最佳途径之一。” => 我不完全同意 - javadoc(类的契约)才是最好的方法。您在代码中找到的内容只回答了特定实现的问题,但代码可能会改变。 - assylias
4
例如,哈希表中的此变量在JDK 6中是volatile的,但在Java 7中不再是volatile的。如果你的代码基于这个变量是volatile的事实,那么当你升级JDK时它将会出问题...诚然,这个例子是不同的,但你明白了重点。 - assylias
CAS是某个标准缩写吗? - abbas
1
比较并交换 - endless

4

AtomicReference 提供了比普通的 volatile 变量更多的功能。当您阅读 API Javadoc 时,您会知道这一点,但它还提供了一个锁,对于某些操作非常有用。

然而,除非您需要此额外功能,否则建议使用普通的 volatile 字段。


因此,它们的区别在于它们的性能。如果没有区别,你永远不会建议使用其中一个而不是另一个。 - B T
性能基本相同。AtomicReference 增加了复杂性和内存使用量。 - Peter Lawrey
@BT volatile 字段可以像任何常规字段一样使用,而访问 AtomicReference 中的值需要通过 getset 方法。 - David Harkness

0
有时即使您只使用gets和sets,AtomicReference也可能是一个不错的选择:
带有volatile的示例:
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 someone 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;

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


不确定为什么这被投票否决了。它确实展示了一个有效的例子,status 的值在 if 测试和 println 语句中使用之间可能会发生变化。我不确定在清理期间哪个版本更有可能被移除。 - LordOfThePigs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接