在什么情况下需要在Java中使用AtomicReference?

14 浏览
0 Comments

在什么情况下需要在Java中使用AtomicReference?

我们什么时候使用AtomicReference

在所有多线程程序中都需要创建对象吗?

请提供一个简单的示例,说明应该在何处使用AtomicReference。

admin 更改状态以发布 2023年5月21日
0
0 Comments

原子引用是在需要通过用新的拷贝(不可变对象)更新多个线程共享的内容时使用的理想方法。这是一种密集的声明,因此我将对其进行分解。

首先,不可变对象是指在构造之后不会实际更改的对象。经常,不可变对象的方法会返回同一类型的新实例。一些例子包括包装类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 AtomicReference 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);
}

现在为什么这更好呢?实际上,该代码略微不太干净。但是,在AtomicReference下发生了一些非常重要的事情,那就是compare and swap。

它是一条CPU指令,不是操作系统调用,使交换发生。这是CPU上的一条指令。由于没有锁定,因此在锁定被使用的情况下没有上下文切换,从而节省更多时间!

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

0
0 Comments

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

AtomicReference cache = new AtomicReference();
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);


由于原子引用语义,即使cache对象在线程之间共享,也可以在不使用synchronized的情况下执行此操作。一般来说,除非您知道自己在做什么,否则最好使用同步器或java.util.concurrent框架而不是裸的Atomic*
两本介绍这个主题的杰出纸质参考资料:

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

0