【问题标题】:AtomicReference in Java - necessary for setting a reference in a thread-safe environment?Java中的AtomicReference - 在线程安全环境中设置引用所必需的?
【发布时间】:2026-02-12 18:15:02
【问题描述】:

在 Java 中存在一个 AtomicReference 类。这是否意味着设置引用本身不是原子操作?

例如,这不是线程安全的吗(假设返回的值不能被修改)?:

public void someMethod()
{
   this.someList = Collections.unmodifiableList(new LinkedList<Object>());
}

public List<Object> getReadOnlyList()
{
   return someList;
}

在 C# 中怎么样?

【问题讨论】:

  • 您的示例并不完全是线程安全的。如果列表在此处不安全地发布,您可能无法正确阅读内容(尽管LinkedList 的实现可能允许您在它为空的情况下逃脱)。
  • @Tom :您的意思是其他线程可能会为 someList 字段获取旧的缓存值,或者 someList 字段实际上可能是“半写的”?
  • @hatchetman82 我的意思是在读取新引用后,它指向的对象可能没有完全更新。因此,如果我不安全地公开new java.awt.Point(1, 2),我可能能够在另一个线程中读取该对象,但xy 字段可能仍然为零。

标签: c# java atomicreference


【解决方案1】:

这是否意味着设置引用本身不是原子操作?

设置引用变量是原子的,但原子操作不一定是线程安全的。让我解释一下。

原子意味着任何观察者(线程)看到变量的旧值或新值,而不是别的。这并不意味着所有观察者在查看变量时都会看到新值。 (正如@Tom 指出的那样,引用变量的原子性并没有说明它引用的对象的原子性属性。)

为了让所有观察者都能看到变量中的新值,需要进行一些同步。对于变量的更新,这将在以下情况下发生:

  • 变量声明为volatile,或者
  • 对变量的访问/更新由同一个原始监视器锁同步。

包装在相关“AtomicXxx”类中的变量也将是线程安全的,但如果您想避免锁并且您想要执行诸如此类的操作,您通常会使用这些类之一作为原子的“比较和替换”。

同样,这仅适用于对象引用的线程安全。如果对象的状态没有正确同步,线程很可能会看到对象属性的陈旧值等等。

【讨论】:

    【解决方案2】:

    java.util.concurrent.atomic 有时被忽视的package description 详细阐述了一些常见用途。

    附录:同样,java.util.concurrentpackage description 方便地总结了JLS §17 中详述的几个要点。

    此外,如果您的 List 是不可变的并且可以通过 final 引用它,请考虑 Final Field Semantics 的潜在好处。

    【讨论】:

      【解决方案3】:

      根据Java Language Specification, version 3.0, Section 17.7

      写信给 并且对引用的读取始终是原子的,无论它们是否被实现 作为 32 或 64 位值。

      AtomicReference 可以执行比较并设置为原子操作。

      这不是线程安全的:

      public boolean changeList(List<Object> oldValue, List<Object> newValue) { 
          if (this.someList == oldValue) {
              // someList could be changed by another thread after that compare,
              // and before this set
              this.someList = newValue;
              return true;
          }
          return false;
      }
      

      【讨论】:

      • 可以在 List 参数中使用一些泛型。
      • 使用 public boolean changeList(List oldValue, List newValue) 代替
      【解决方案4】:

      就 C# 而言,我自己找到了答案。根据 C# 语言规范的第 5.5 节,设置引用是一项原子操作。

      "以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float和引用类型。此外,对具有底层类型的枚举类型的读写上一个列表中的也是原子的。其他类型的读写,包括long、ulong、double和decimal以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,不保证原子性的读-修改-写,例如在递增或递减的情况下。”

      【讨论】:

      • 问题是关于线程安全,而不是原子性。例如,在 Java 中读取非同步引用变量可以是原子的,但不是线程安全的。
      • 原子性保证您将读取引用的旧值(或 null)或引用的新值,但绝不会是其中一个的一部分和另一个的一部分。
      【解决方案5】:

      如果您不使用 AtomicReference 或 volatile 关键字,并且读取引用的线程不是写入它的线程,则无法保证读取线程会看到更新的值。

      在多处理器环境中尤其如此。 volatile 关键字和 AtomicReference(在内部使用 volatile 进行基本的设置/获取操作)强制执行内存屏障和缓存刷新,以确保更新的值在主内存中可见。

      【讨论】: