何时在 Java 中使用 AtomicReference?

2022-08-31 04:40:55

我们什么时候使用原子引用

是否需要在所有多线程程序中创建对象?

提供一个应使用原子引用的简单示例。


答案 1

原子引用应该在需要对引用执行简单原子(即线程安全、非平凡)操作的设置中使用,对于这种操作,基于监视器的同步是不合适的。假设仅当对象的状态在处理过程中发生更改时才要设置特定字段:

AtomicReference<Object> cache = new AtomicReference<Object>();

Object cachedValue = new Object();
cache.set(cachedValue);

//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);

由于原子引用语义,即使对象在线程之间共享,也可以执行此操作,而无需使用 。一般来说,你最好使用同步器或框架,而不是裸露,除非你知道你在做什么。cachesynchronizedjava.util.concurrentAtomic*

两个优秀的死树参考,将向您介绍这个主题:

请注意(我不知道这是否一直都是真的)引用赋值(即)本身是原子的(更新原始的64位类型,如或可能不是原子的;但更新引用始终是原子的,即使它是64位),而没有显式使用。
请参阅 Java 语言规范 3ed, 第 17.7 节=longdoubleAtomic*


答案 2

当您需要在多个线程之间共享和更改不可变对象的状态时,非常适合使用原子引用。这是一个超级密集的声明,所以我将稍微分解一下。

首先,不可变对象是构造后实际上不会更改的对象。通常,不可变对象的方法返回同一类的新实例。一些例子包括包装类和,以及,仅举几例。(根据JVM上的编程并发,不可变对象是现代并发的关键部分。LongDoubleString

接下来,为什么分享这种共享价值比对象更好?一个简单的代码示例将显示差异。AtomicReferencevolatile

volatile String sharedValue;

static final Object lock = new Object();

void modifyString() {
    synchronized (lock) {
        sharedValue = sharedValue + "something to add";
    }
}

每次要根据该易失性字段的当前值修改其引用的字符串时,首先需要获取该对象的锁定。这可以防止其他线程在此期间进入并更改新字符串串联中间的值。然后,当您的线程恢复时,您将删除其他线程的工作。但老实说,这些代码会起作用,它看起来很干净,它会让大多数人开心。

轻微的问题。它很慢。特别是如果该锁对象存在大量争用。这是因为大多数锁都需要操作系统系统调用,并且您的线程将阻塞并将上下文从CPU中切换出来,以便为其他进程让路。

另一个选项是使用原子折射。

public static AtomicReference<String> shared = 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);
}

为什么这样更好呢?老实说,该代码比以前更不干净。但是在AtomicRefrence中,有一些非常重要的事情发生在引擎盖下,那就是比较和交换。它是单个CPU指令,而不是操作系统调用,使切换发生。这是 CPU 上的单个指令。而且由于没有锁,因此在执行锁的情况下没有上下文切换,从而节省了更多时间!

问题是,对于 AtomicReferences,这不使用调用,而是使用预期值的比较。因此,请确保预期的是从 get in 循环返回的实际对象。.equals()==