为什么scala会挂起在Future中评估一个名字参数?

Sia*_*iam 3 multithreading scala future object pass-by-name

下面(设计的)代码尝试在将来打印一个名字的String参数,并在打印完成时返回.

import scala.concurrent._
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

class PrintValueAndWait {
  def printIt(param: => String): Unit = {
    val printingComplete = future { 
      println(param);  // why does this hang?
    }
    Await.result(printingComplete, Duration.Inf)
  }
}

object Go {
  val str = "Rabbits"

  new PrintValueAndWait().printIt(str)
}

object RunMe extends App {
  Go
}
Run Code Online (Sandbox Code Playgroud)

但是,在运行时RunMe,它只是在尝试评估时挂起param.更改printIt为接受其参数的值会使应用程序按预期返回.或者,更改printIt为简单地打印值并同步返回(在同一个线程中)似乎也可以正常工作.

这到底发生了什么?这是否与Go对象有关尚未完全构造,因此该str字段尚未显示给尝试打印它的线程?挂在这里的预期行为?

我已经在Java 1.7上使用Scala 2.10.3在Mac OS Mavericks和Windows 7上进行了测试.

kon*_*ong 10

您的代码在Go对象初始化时死锁.这是一个已知问题,参见例如SI-7646和这个SO问题

scala中的对象被懒惰地初始化,并且在此期间进行锁定以防止两个线程竞争以初始化对象.但是,如果两个线程同时尝试初始化一个对象而另一个线程依赖另一个完成,则会出现循环依赖关系和死锁.

在这种特殊情况下,Go对象的初始化只能在完成一次后new PrintValueAndWait().printIt(str)完成.但是,如果param是by name参数,则基本上传递一个代码块,在使用它时对其进行求值.在这种情况下,str参数in new PrintValueAndWait().printIt(str)是简写Go.str,所以当未来运行的线程尝试评估param它实质上是调用Go.str.但由于Go还没有完成初始化,它也会尝试初始化Go对象.另一个线程初始化Go会锁定其初始化,因此将来的线程会阻塞.所以第一个线程在完成初始化之前等待将来完成,并且未来的线程正在等待第一个线程完成初始化:死锁.

在by值的情况下,字符串值str直接传入,因此未来的线程不会尝试初始化Go并且没有死锁.

同样,如果您param按名称离开,但更改Go如下:

object Go {
  val str = "Rabbits"

  {
    val s = str
    new PrintValueAndWait().printIt(s)
  }
}
Run Code Online (Sandbox Code Playgroud)

它不会死锁,因为已经计算过的本地字符串值s是传入的,而不是Go.str,所以未来的线程不会尝试初始化Go.