Rom*_*kiy 5 multithreading scala lazy-evaluation
在他的一个视频中(关于Scala的懒惰评估,即lazy关键字),Martin Odersky展示了以下cons用于构建的操作的实现Stream:
def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
def head = hd
lazy val tail = tl
...
}
Run Code Online (Sandbox Code Playgroud)
因此,tail使用该语言的惰性评估功能简明扼要地编写操作.
但实际上(在Scala 2.11.7中),实现tail有点不那么优雅:
@volatile private[this] var tlVal: Stream[A] = _
@volatile private[this] var tlGen = tl _
def tailDefined: Boolean = tlGen eq null
override def tail: Stream[A] = {
if (!tailDefined)
synchronized {
if (!tailDefined) {
tlVal = tlGen()
tlGen = null
}
}
tlVal
}
Run Code Online (Sandbox Code Playgroud)
双重检查锁定和两个易失性字段:这大致是如何在Java中实现线程安全的惰性计算.
所以问题是:
lazyScala的关键字在多线程情况下是否提供任何"评估的最大一次"保证?tail实现中使用的模式是否是在Scala中进行线程安全的惰性求值的惯用方法?Scala 的惰性关键字在多线程情况下不提供任何“最多评估一次”保证吗?
是的,确实如此,正如其他人所说。
真实尾部实现中使用的模式是在 Scala 中进行线程安全惰性求值的惯用方法吗?
我想我已经找到了为什么不这样做的实际lazy val答案。Stream具有面向公众的 API 方法,例如hasDefinitionSize继承自TraversableOnce. 为了知道 aStream的大小是否有限,我们需要一种在不具体化底层 Stream尾部的情况下进行检查的方法。由于lazy val实际上并没有暴露底层位,所以我们不能这样做。
这是由SI-1220支持的
为了强调这一点,@Jasper-M指出LazyListstrawman中的新api(Scala 2.13集合改造)不再有这个问题,因为整个集合层次结构已经被重新设计,不再有这样的问题。
我想说“这取决于”你从哪个角度看待这个问题。从 LOB 的角度来看,lazy val为了实现的简洁性和清晰度,我肯定会选择使用。但是,如果您从 Scala 集合库作者的角度来看,事情就会开始变得不同。可以这样想,您正在创建一个库,该库可能会被许多人使用并在世界各地的许多机器上运行。这意味着您应该考虑每个结构的内存开销,特别是如果您自己创建这样一个重要的数据结构。
我这样说是因为当您使用 时lazy val,根据设计您会生成一个附加Boolean字段,该字段会标记该值是否已初始化,并且我假设这是库作者想要避免的情况。JVM 上a 的大小Boolean当然取决于 VM,即使是一个字节也是需要考虑的,特别是当人们生成大量Stream数据时。再说一次,这绝对不是我通常会考虑的事情,而且绝对是对内存使用的微观优化。
我认为性能是关键点之一的原因是SI-7266,它修复了 Stream 中的内存泄漏。请注意跟踪字节代码以确保生成的类中不保留任何额外值的重要性。
实现上的区别在于,tail是否初始化的定义是检查生成器的方法实现:
def tailDefined: Boolean = tlGen eq null
Run Code Online (Sandbox Code Playgroud)
而不是类中的一个字段。