在 Scala 中仅更新单个 var 线程的 class 实例是否安全?

Is a class instance that updates only a single var thread safe in Scala?

我需要在多线程环境中在 Scala 中缓存一些东西。

阅读 scalaz 的 Memo 我在 the code 中发现了以下关于不可变哈希映射备忘录的评论:

As this memo uses a single var, it's thread-safe.

代码如下所示:

  def immutableMapMemo[K, V](m: Map[K, V]): Memo[K, V] = {
    var a = m

    memo[K, V](f =>
      k => {
        a get k getOrElse {
          val v = f(k)
          a = a updated (k, v)
          v
        }
      })
  }

说这是线程安全的与我到目前为止所阅读和了解的关于 JVM 平台上的线程安全的知识背道而驰;引用更新可能是原子的,但据我所知,如果您没有内存屏障,编译器可能会尝试进行某些优化,从而破坏发生在之前的关系。例如参见 [​​=16=].

但我相信 scalaz 的人非常聪明。也许 a.

的范围有一些特别之处

评论所说的是真的吗?如果是,为什么?

首先,由于 var 没有标记 @volatile,您可能会在不同的线程中看到不同版本的 a。所以你可能会在不同的线程上多次计算。这种违背了记忆的目的,但除此之外它不会造成任何伤害,前提是被记忆的功能没有副作用。

此外,在 x86 架构上,您几乎总是会看到在一个线程上完成的更改会在所有其他线程上完成。

关于地图的内部一致性:据我所知,在这种情况下不可能观察到存储在不一致状态的地图,因为地图是不仅可观察到不可变,而且所有版本的 Map(Map1、Map2、Map3、Map4、HashMap1、HashTrieMap、HashMapCollision1、EmptyMap)只有 final 字段,因此根据 java 内存模型是安全的。 但是,靠这个是极其脆弱的

例如,如果 a 将包含一个 List 或一个 Vector,您 在从不同线程快速更新它时能够观察到它处于不一致的状态。这样做的原因是这些数据结构在观察上是不可变的,但是确实在内部使用可变状态来进行性能优化。

所以底线:不要在多线程上下文中依赖它进行记忆。

请参阅 this thread 在 scala-user 上讨论一个非常相似的问题

请参阅 this thread 了解为什么即使是基本的可观察不可变数据结构(例如 List 和 Vector)也可以在不一致的状态下被观察到,除非通过 @volatile 或其他安全机制(例如 actor)使用安全发布。