在什么情况下需要在Java中使用AtomicReference?
在什么情况下需要在Java中使用AtomicReference?
我们什么时候使用AtomicReference
?
在所有多线程程序中都需要创建对象吗?
请提供一个简单的示例,说明应该在何处使用AtomicReference。
原子引用是在需要通过用新的拷贝(不可变对象)更新多个线程共享的内容时使用的理想方法。这是一种密集的声明,因此我将对其进行分解。
首先,不可变对象是指在构造之后不会实际更改的对象。经常,不可变对象的方法会返回同一类型的新实例。一些例子包括包装类Long和Double,以及String等等(根据《Java虚拟机上的程序并发》,不可变对象是现代并发的关键部分)。
接下来,为什么AtomicReference比volatile对象更适合共享共享值?一个简单的代码示例将显示差异。
volatile String sharedValue; static final Object lock = new Object(); void modifyString() { synchronized (lock) { sharedValue = sharedValue + "something to add"; } }
每次您想要修改由volatile字段引用的字符串的内容时,都需要先锁定该对象。这可以防止在新字符串连接的同时进入其他线程并在其中间更改值。然后,当您的线程恢复时,会覆盖另一个线程的工作。但是,实际上该代码可以工作,看起来很干净,大多数人都会满意。
稍微有点问题。它很慢。特别是如果该锁对象的争用很多时。这是因为大多数锁需要一个操作系统系统调用,而您的线程将被阻塞并被上下文切换出CPU,以便为其他进程腾出空间。
另一个选择是使用AtomicReference。
public static AtomicReferenceshared = new AtomicReference<>();
String init = "Inital Value"; shared.set(init); //now we will modify that value boolean success = false; while (!success) { String prevValue = shared.get(); // do all the work you need to String newValue = shared.get() + "let's add something"; // Compare and set success = shared.compareAndSet(prevValue, newValue); }
现在为什么这更好呢?实际上,该代码略微不太干净。但是,在AtomicReference下发生了一些非常重要的事情,那就是compare and swap。
它是一条CPU指令,不是操作系统调用,使交换发生。这是CPU上的一条指令。由于没有锁定,因此在锁定被使用的情况下没有上下文切换,从而节省更多时间!
问题在于,对于AtomicReferences,这不使用.equals()调用,而是使用==比较来进行预期值的比较。因此,请确保预期值是在循环中从get返回的实际对象。
原子引用应该在需要对引用进行简单原子(即线程安全、非平凡)操作的设置中使用,即监视器基于同步不适合的场合。假设在处理期间仅当对象状态发生更改时才希望设置特定字段:
AtomicReference
由于原子引用语义,即使cache
对象在线程之间共享,也可以在不使用synchronized
的情况下执行此操作。一般来说,除非您知道自己在做什么,否则最好使用同步器或java.util.concurrent
框架而不是裸的Atomic*
。
两本介绍这个主题的杰出纸质参考资料:
请注意,(我不知道这是否一直如此)引用赋值(即=
)本身是原子的(更新原始64位类型如long
或double
可能不是原子的,但更新引用始终是原子的,即使它是64位) ,而无需显式使用Atomic*
。
请参阅Java语言规范3ed,第17.7节。