何时在 Java 中使用 AtomicReference?

新手上路,请多包涵

我们什么时候使用 AtomicReference

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

提供一个应该使用 AtomicReference 的简单示例。

原文由 Chintu 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 657
2 个回答

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

 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);

由于原子引用语义,即使 cache 对象在线程之间共享,您也可以这样做,而不使用 synchronized 。一般来说,你最好使用同步器或 java.util.concurrent 框架而不是裸 Atomic* 除非你知道你在做什么。

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

请注意(我不知道这是否一直是真的) 引用 分配(即 = )本身是原子的(更新 原始 64 位类型,如 longdouble 可能不是原子的;但更新 引用 始终是原子的,即使它是 64 位的)没有明确使用 Atomic*

请参阅 Java 语言规范 3ed, 第 17.7 节

原文由 andersoj 发布,翻译遵循 CC BY-SA 4.0 许可协议

当您需要更新多个线程访问的内容(在不可变对象中)时,原子引用是理想的选择,方法是将其替换为这些线程之间共享的(不可变对象的)新副本。这是一个超级密集的陈述,所以我会把它分解一下。

首先,不可变对象是在构造后实际上不会更改的对象。通常,不可变对象的方法会返回同一类的新实例。一些示例包括包装类 LongDouble 以及 String ,仅举几例。 (根据 Programming Concurrency on the JVM ,不可变对象是现代并发的关键部分。)

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

 volatile String sharedValue;

static final Object lock = new Object();

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

每次你想根据它的当前值修改那个 volatile 字段引用的字符串时,你首先需要获得对该对象的锁定。这可以防止其他线程在此期间进入并更改新字符串连接中间的值。然后当你的线程恢复时,你破坏了另一个线程的工作。但老实说,该代码可以工作,看起来很干净,而且会让大多数人满意。

轻微问题。它很慢。特别是如果该锁对象有很多争用。那是因为大多数锁都需要 OS 系统调用,并且您的线程将阻塞并被上下文切换出 CPU 以为其他进程让路。

另一种选择是使用 AtomicReference。

 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,这不使用 .equals() 调用,而是使用 == 比较预期值。因此,请确保 expected 是循环中从 get 返回的实际对象。

原文由 Erik Helleren 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题