5

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

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

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

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

在 C# 中怎么样?

4

5 回答 5

3

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

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

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

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

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

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

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

于 2010-03-05T04:20:09.057 回答
3

根据Java 语言规范,版本 3.0,第 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;
}
于 2010-03-05T03:08:54.620 回答
3

有时被忽视的包描述详细说明了java.util.concurrent.atomic一些常见用途。

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

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

于 2010-03-05T03:21:09.737 回答
2

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

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

于 2010-03-05T03:09:04.410 回答
1

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

“以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float和引用类型。另外,前面提到的带有底层类型的枚举类型的读写list 也是原子的。其他类型的读写,包括 long、ulong、double 和 decimal,以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,没有原子读取-修改-写入的保证,例如在递增或递减的情况下。”

于 2010-03-05T03:24:23.677 回答