volatile
对象引用和AtomicReference
我只使用get()
and set()
-methods from之间有什么区别AtomicReference
吗?
6 回答
简短的回答是:不。
从java.util.concurrent.atomic
包文档。去引用:
原子访问和更新的记忆效应通常遵循 volatile 的规则:
get
具有读取volatile
变量的记忆效应。set
具有写入(分配)volatile
变量的记忆效应。
顺便说一句,该文档非常好,并且对所有内容都进行了解释。
AtomicReference::lazySet
是一种较新的(Java 6+)操作,其语义无法通过volatile
变量实现。有关更多信息,请参阅此帖子。
不,那里没有。
AtomicReference 提供的额外功能是 compareAndSet() 方法和朋友。如果您不需要这些方法,可变引用提供与 AtomicReference.set() 和 .get() 相同的语义。
有几个区别和权衡:
使用
AtomicReference
get/set 具有与 volatile 字段相同的JMM 语义(如 javadoc 所述),但它AtomicReference
是引用的包装器,因此对该字段的任何访问都涉及进一步的指针追踪。内存占用成倍增加(假设一个压缩的OOP 环境,对于大多数 VM 来说都是如此):
- 易失性参考 = 4b
AtomicReference
= 4b + 16b(12b 对象头 + 4b 参考字段)
AtomicReference
提供比 volatile 参考更丰富的 API。AtomicFieldUpdater
您可以使用或使用 Java 9 a重新获得 volatile 引用的 APIVarHandle
。sun.misc.Unsafe
如果你喜欢用剪刀跑步,你也可以直接伸手去拿。AtomicReference
本身是使用Unsafe
.
那么,什么时候最好选择一个而不是另一个:
- 只需要获取/设置?坚持使用易变字段、最简单的解决方案和最低的开销。
- 需要额外的功能?
AtomicReference
如果这是代码的性能(速度/内存开销)敏感部分AtomicFieldUpdater
,请Unsafe
在您倾向于为可读性和性能增益风险付出代价之间做出选择。如果这不是一个敏感区域,那就去吧AtomicReference
。库编写者通常根据目标 JDK、预期的 API 限制、内存限制等混合使用这些方法。
JDK 源代码是解决此类困惑的最佳方法之一。如果您查看 AtomicReference 中的代码,它使用 volatie 变量进行对象存储。
private volatile V value;
所以,很明显,如果你打算只在 AtomicReference 上使用 get() 和 set() ,那就像使用 volatile 变量一样。但正如其他读者评论的那样,AtomicReference 提供了额外的 CAS 语义。因此,首先决定是否需要 CAS 语义,如果只需要,则使用 AtomicReference。
AtomicReference
提供了普通 volatile 变量不提供的附加功能。当您阅读过 API Javadoc 时,您会知道这一点,但它还提供了一个锁,这对某些操作很有用。
但是,除非您需要此附加功能,否则我建议您使用普通volatile
字段。
有时即使您只使用获取和设置,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;
然而,第二个更有可能在将来“代码清理”期间被某人意外删除。