【问题标题】:What object do I synchronize on in Scala?我在 Scala 中同步什么对象?
【发布时间】:2013-06-12 08:53:39
【问题描述】:

在 C# 中非常简单:

class Class1{
  private static readonly object locker = new object();
  void Method1(){
    lock(locker) { .... }
  }
}

我绝对不应该在this 上进行同步,因为这可能会导致死锁。同样,在 Scala 中,我看到了一些示例,但无法理解同步的基本原理和我应该用来进行同步的对象(字段):

#1
def add(user: User) {
  // tokenizeName was measured to be the most expensive operation.
  val tokens = tokenizeName(user.name)

  tokens.foreach { term =>
    userMap.synchronized {
      add(term, user)
    }
  }
}

#2
class Person(var name: String) {
  def set(changedName: String) {
    this.synchronized {
      name = changedName
    }
  }
}

#3 and so on...

你介意我说清楚吗?

【问题讨论】:

  • 虽然在 scala 中可以进行同步,但消除对它的需要会更符合习惯。例如,使用不可变数据结构并以允许非阻塞异步执行的方式设计程序。
  • @rompetroll,我很好奇。你能给我举个例子吗?但尽管如此,即使使用不可变数据结构也只是将同步的责任进一步转移,例如,对于将使用具有不可变状态的代码的人,最终将有必要使用它。据我了解,即使存在 only 不可变数据结构,也无法避免使用synchronized 方法。不是这样吗?
  • 如果你的程序没有分叉,你为什么需要synchronized?如果您的子例程都基于不可变结构,那么唯一可以看到状态更新的地方就是您的 main 方法。您可以完美地设计大型库,而无需引入单个 synchronized 关键字。
  • @Grienders,不,完全有可能。如果您的数据完全不可变(no 例外;这也意味着没有执行 I/O 操作),那么实际上不需要显式同步,因为没有线程将能够以任何方式干扰其他线程操作。
  • 但是,使用不可变数据,您可以在线程之间传输数据的唯一方法是使用新数据启动新的子线程。这可能非常不方便且无效,因此您最终只需要具有可变状态即可。但是不同的库试图抽象这个状态,所以你不会直接访问它,所以你仍然不需要synchronized。例如,这就是演员库(例如 Akka)所做的。在较低级别上,每个参与者都有 可变队列 消息,但您不能直接访问它,而是使用 不可变消息。因此,您的代码中没有synchronized

标签: java multithreading scala


【解决方案1】:

在 Scala 中,获得相同的行为更加直接(我假设您出于某种原因想要锁定包含的对象,例如,比锁定该类的整个实例更细粒度的控制):

class Class1 {
  private object Locker
  def method1 { Locker.synchronized { ... } }
}

但你不应该以这种方式控制事物。特别是,如果不关注... 中的内容,它不会防止 C# 或 Scala 中的死锁。

您至少应该使用java.util.concurrent 中的并发工具,并且您可能想要研究期货或演员。

【讨论】:

  • @Grienders 这是 Scala,而且确实是制作私有锁的正确方法,这降低了死锁的风险,因为Class1 完全控制了该锁的使用位置.如果你只有一个私有锁,你就不会陷入死锁。您至少需要两把锁才能避免潜在的死锁。
  • @Grienders - 那是 Scala。您说“在 C# 中它非常简单”。我展示了如何更简单地在 Scala 中做同样的事情。因此,如果您已经知道如何在 C# 中执行此操作,那么现在您知道如何在 Scala 中执行此操作了。
  • @0__ - 您确实需要至少两个锁,但它们可以是同一类的不同实例上的两个锁。因此,即使只有一把私人锁,您也必须非常小心。
  • 相信我,多年来我一直在编写 Scala。这编译。我认为您删除省略号 ... 这是一个占位符。
  • @Rex 完全正确。顺便说一句,我使用private val lock = new AnyRef,看起来不太漂亮,但不会引入内部类的开销。
【解决方案2】:
  1. 在 Scala 中锁定 object 与在 Java 中锁定静态字段/类相同,这基本上是“最难”的锁定之一。它不会阻止对类实例的操作,而是在类加载器范围内对类本身进行操作。在引入这样的锁时,您应该仔细考虑。由于获取的锁的顺序不正确,它不会保护您免受死锁,而是会导致线程阻塞,如果线程正在处理类的不同实例,并且可能根本不会干扰。

  2. 锁定“this”或某个类(非对象)字段(互斥体)是更轻松的同步方式,您应该使用它来管理对类的访问,而不是对此类的特定实例的访问。

  3. 看看akka 中的演员,他们摇滚并消除了许多同步问题。

旁注:在“this”上进行同步并不意味着死锁。

【讨论】:

  • making synchronization on 'this' doesn't imply deadlocks. - 在 Java 中?在 C# 中它可能会。
  • 我不知道仅仅让它在类实例上同步会导致死锁。如果您将实例 A 锁定在线程 B 中,然后尝试锁定实例 C,然后将实例 C 锁定在线程 D 中并尝试锁定实例 A - 那么您将遇到麻烦,而不管对象 A 和 C(静态与否)。
  • 在“this”上进行同步并不意味着死锁。 ——在Java中?在 C# 中它可能会这样做
  • 我认为 #1 中的单词选择不仔细(即错误)。 Scala 对象类似于 Java 静态,但它只是底层的一个普通实例,一个类似于 Foo$.MODULE$ 的静态字段。 Foo.synchronized 表示在该实例上同步,而不是在 Foo$.class 上。
  • 我不了解 C#,但是在 this 上进行同步不会自动在 Java 或 Scala 中引入死锁风险。这取决于你对他们做什么。如果一个线程可以在实例 A 和实例 B 上同步(按此顺序),而另一个线程在实例 B 和实例 A 上同步,那么您就有麻烦了。
【解决方案3】:

如果前提是您希望避免锁定 this,因为使用第三方代码的另一个线程可以锁定同一对象,那么 Scala 提供了更高级别的可见性 private[this]

class C {
  private[this] val lock = new Object()
  def method1(): Unit = lock.synchronized {
    println("method1")
  }
}

实际上,除了C 的特定实例之外,没有其他对象可以访问lock。甚至同一类的其他实例也无法访问lock

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-24
    • 1970-01-01
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    相关资源
    最近更新 更多