【问题标题】:Is a class instance that updates only a single var thread safe in Scala?在 Scala 中只更新单个 var 线程的类实例是否安全?
【发布时间】:2015-11-12 12:46:39
【问题描述】:

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

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

由于本备忘录使用单个 var,因此它是线程安全的。

代码如下:

  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 平台上的线程安全的知识相悖;参考更新可能是原子的,但据我所知,如果您没有内存屏障,编译器可能会尝试进行某些优化,从而扰乱发生之前的关系。参见例如this postthis

但我敢肯定 scalaz 的人很聪明。也许a 的范围有什么特别之处。

评论所声称的是否属实,如果是,为什么?

【问题讨论】:

  • 根据您的经验,这里实际上对线程安全有什么影响? var 更新是引用分配,所以它是原子的。所有其他操作都不会改变对象,因此a 中的任何时候都有Map[k,V] 的一些一致版本
  • 我会更新我的问题以澄清。

标签: scala hashmap thread-safety scalaz memoization


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    相关资源
    最近更新 更多