我在我的真实项目中遇到过这个问题,并通过我的测试代码和分析器证明了这一点。我没有粘贴“tl; dr”代码,而是向您展示一张图片,然后对其进行描述。
简单地说,我正在使用Future.firstCompletedOf
从2 Future
得到一个结果,这两个结果都没有共享内容而且彼此不关心。即使这是我想要解决的问题,垃圾收集器也无法回收第一个Result
对象,直到Future
完成。
所以我对这背后的机制非常好奇。有人可以从较低的层面解释它,或提供一些提示让我调查。
谢谢!
PS:是因为他们共享相同的ExecutionContext
?
**按要求更新**粘贴测试代码
object Main extends App{
println("Test start")
val timeout = 30000
trait Result {
val id: Int
val str = "I'm short"
}
class BigObject(val id: Int) extends Result{
override val str = "really big str"
}
def guardian = Future({
Thread.sleep(timeout)
new Result { val id = 99999 }
})
def worker(i: Int) = Future({
Thread.sleep(100)
new BigObject(i)
})
for (i <- Range(1, 1000)){
println("round " + i)
Thread.sleep(20)
Future.firstCompletedOf(Seq(
guardian,
worker(i)
)).map( r => println("result" + r.id))
}
while (true){
Thread.sleep(2000)
}
}
答案 0 :(得分:10)
让我们看看如何实施firstCompletedOf
:
def firstCompletedOf[T](futures: TraversableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = {
val p = Promise[T]()
val completeFirst: Try[T] => Unit = p tryComplete _
futures foreach { _ onComplete completeFirst }
p.future
}
执行{ futures foreach { _ onComplete completeFirst }
时,函数{ _ onComplete completeFirst }
会保存在某处
通过ExecutionContext.execute
。保存此函数的确切位置无关紧要,我们只知道它必须保存在某个地方
这样当线程可用时,它可以在以后被选中并在线程池上执行。
此功能关闭completeFirst
,关闭p
。
因此,只要还有一个未来(来自futures
)等待完成,就会引用p
来阻止它被垃圾收集(即使到那时候可能是firstCompletedOf
1}}已经返回,从堆栈中删除p
。
当第一个未来完成时,它会将结果保存到承诺中(通过调用p.tryComplete
)。
由于承诺p
包含结果,因此至少只要p
是可访问的,结果就是可达的,并且正如我们所看到的那样p
只要至少有一个来自futures
即可到达{1}}尚未完成。
这就是为什么在所有期货完成之前无法收集结果的原因。
<强>更新强>: 现在的问题是:它可以修复吗?我认为可以。我们所要做的就是确保第一个未来完成&#34;归零&#34;以线程安全的方式引用p,可以通过使用AtomicReference的示例来完成。像这样:
def firstCompletedOf[T](futures: TraversableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = {
val p = Promise[T]()
val pref = new java.util.concurrent.atomic.AtomicReference(p)
val completeFirst: Try[T] => Unit = { result: Try[T] =>
val promise = pref.getAndSet(null)
if (promise != null) {
promise.tryComplete(result)
}
}
futures foreach { _ onComplete completeFirst }
p.future
}
我已对其进行了测试,并且正如预期的那样,它确实允许在第一个未来完成后立即对结果进行垃圾回收。它应该在所有其他方面表现相同。