Scala:对象初始值设定项中的并行集合导致程序挂起

Scala:对象初始值设定项中的并行集合导致程序挂起,scala,scala-collections,Scala,Scala Collections,我刚刚注意到一种令人不安的行为。 假设我有一个独立的程序,由一个单独的对象组成: object ParCollectionInInitializerTest { def doSomething { println("Doing something") } for (i <- (1 to 2).par) { println("Inside loop: " + i) doSomething } def main(args: Array[String]) {

我刚刚注意到一种令人不安的行为。 假设我有一个独立的程序,由一个单独的对象组成:

object ParCollectionInInitializerTest {
  def doSomething { println("Doing something") }

  for (i <- (1 to 2).par) {
    println("Inside loop: " + i)
    doSomething
  }

  def main(args: Array[String]) {
  }
}
对象ParCollectionInInitializerTest{
def doSomething{println(“做某事”)}

对于(i,这是一个固有的问题,当在构造完成之前释放对singleton对象的引用时,Scala中可能会发生这种问题。这是由于不同的线程试图在对象完全构造之前访问对象
ParcollectionInInitializeTest
。 它与
main
方法无关,而是与初始化包含
main
方法的对象有关——试着在REPL中运行它,键入表达式
parcollectionInInitializeTest
,您将得到相同的结果。它也与fork-join工作线程无关是守护进程线程

单例对象是惰性初始化的。每个单例对象只能初始化一次。这意味着访问该对象的第一个线程(在您的情况下,是主线程)必须获取对象的锁,然后对其进行初始化。随后出现的每个其他线程都必须等待主线程初始化对象并最终释放锁。这是Scala中实现单例对象的方式

在您的例子中,并行收集工作线程尝试访问singleton对象以调用
doSomething
,但在主线程完成初始化对象之前无法调用,因此它会等待。另一方面,主线程在构造函数中等待,直到并行操作完成,这取决于所有工作线程完成——主线程始终持有单例的初始化锁。因此,会发生死锁

您可以使用2.10版的futures或仅使用线程来导致此行为,如下所示:

def execute(body: =>Unit) {
  val t = new Thread() {
    override def run() {
      body
    }
  }

  t.start()
  t.join()
}

object ParCollection {

  def doSomething() { println("Doing something") }

  execute {
    doSomething()
  }

}
将其粘贴到REPL中,然后写入:

scala> ParCollection

REPL挂起。

并发阻塞执行和延迟初始化不能很好地结合在一起。这是Scala(以及Java)中更普遍的问题。请看此SIP:并非反驳,但我确实认为这是开发人员的一个陷阱。我非常感谢您最初的回答和评论,非常感谢。顺便说一句,我相信答案对我来说非常清楚。感谢您添加对相关SIP的参考。相关: