2014-01-17 42 views
10

This answer指示如何java.util.concurrent.Future轉換成scala.concurrent.Future,同時管理將發生阻塞:爲什麼新的線程,而不是未來{...}

import java.util.concurrent.{Future => JFuture} 
import scala.concurrent.{Future => SFuture} 

val jfuture: JFuture[T] = ??? 
val promise = Promise[T]() 
new Thread(
    new Runnable { 
    def run() { promise.complete(Try{ jfuture.get }) } 
    } 
).start 
val future = promise.future 

我queston是相同的意見提出的問題:

future { jfuture.get }有什麼不對?爲什麼你使用一個額外的線程與Promise結合?

有人回答如下:

它會阻止你的拉線線程。如果你爲這樣的期貨有一個配置好的ExecutionContext,那沒問題,但是默認的ExecutionContext包含與你有處理器一樣多的線程。

我不確定我是否理解說明。重申:

future { jfuture.get }怎麼了?未來是否會像手動創建新線程一樣阻塞,並阻止它?如果不是,它有什麼不同?

+0

你究竟有什麼不明白的地方?阻塞線程的含義是什麼? –

+0

@AlexeiKaigorodov我有點修改了我的問題:'future {jfuture.get}'有什麼問題?未來是否會像手動創建新線程一樣阻塞,並阻止它?如果不是,它有什麼不同? –

+0

是在未來的阻擋是AS BAD手動創建一個新的線程和阻塞在那裏。轉換爲'scala.concurrent.Future'的想法是通過使用'onComplete'而不是'get'完全避免阻塞。 –

回答

8

future { jfuture.get }future { future { jfuture.get }}之間幾乎沒有區別。

默認線程池中有許多處理器的處理器。

隨着jfuture.get你會得到1個線程被阻止。

假設您有8個處理器。我們假設每個jfuture.get需要10秒。現在創建8 future { jfuture.get }

val format = new java.text.SimpleDateFormat("HH:mm:ss").format(_: Date) 

val startTime = new Date 
(1 to 8) map {_ => future{ Thread.sleep(10000) }} 
future{ 
    2+2 
    println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}") 
} 

// 2+2 done. Start time: 20:48:18, end time: 20:48:28 

對於2+2評估,10秒有點過長。

所有其他future s和同一個執行上下文中的所有actor將被停止10秒。

帶附加執行上下文:

object BlockingExecution { 
    val executor = ExecutionContext.fromExecutor(new ForkJoinPool(20)) 
} 

def blockingFuture[T](f: => T) = { 
    future(f)(BlockingExecution.executor) 
} 

val startTime = new Date 
(1 to 8) map {_ => blockingFuture{ Thread.sleep(10000) }} 
future{ 
    2+2 
    println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}") 
} 

// 2+2 done. Start time: 21:26:18, end time: 21:26:18 

您可以實現使用new Thread(new Runnable {...blockingFuture,但額外的執行上下文,可以限制線程數。

+0

計算2 + 2的未來需要在其他期貨之後執行,沒有內在原因。 (你只是要求9個事情是異步計算的。)如果你將'Future {Thread.sleep(10000)}'改爲'Future {blocking {Thread.sleep(10000)}}',2 + 2的未來將會立即執行。我的理解是,'blocking'方法給出了一個提示,以便線程池繼續處理其他期貨。我還沒有找到任何描述這裏發生了什麼的好資源。 – Patrick

+0

@帕特里克,這是真的。當使用'blocking'時,全局線程池將爲該計算產生一個新線程,以便它不會耗盡線程。更多信息:http://docs.scala-lang.org/overviews/core/futures.html請參閱「阻止」部分 – simao

7

其實很簡單。 scala.concurrent.PromiseFuture的具體實現,註定是異步計算。

當你想轉換,與jfuture.get,你正在運行阻塞計算和輸出立即解決scala.concurrent.Future

Thread將會阻塞,直到jfuture內部的計算完成。 get方法正在阻止。

阻塞意味着在Thread之內沒有別的東西會發生,直到計算完成。你基本上壟斷了Thread,看起來像一個while循環,間歇性地檢查結果。

while (!isDone() && !timeout) { 
    // check if the computation is complete 
} 

具體來說:

val jfuture: JFuture[T] = ??? // some blocking task 

當阻塞不能避免時,通常的做法是產生一個new Threadnew Runnablenew Callable以允許使計算執行/獨佔一個子線程。

在這個例子中@senia了:

new Thread(new Runnable { def run() { 
    promise.complete(Try{ jfuture.get }) 
}}).start 

這怎麼比future {jfuture.get}不同?它不會阻止Scala提供的默認ExecutionContext,它具有與機器處理器一樣多的線程數。

這意味着代碼中的所有其他期貨將始終需要等待future { jfuture.get }完成,因爲整個上下文都被阻止。

+0

我的印象是產生一個新的線程,並在其中執行'somethingThatBlocks()'與future(somethingThatBlocks())相同。我的問題主要是關於這是否是真的,如果不是,它有什麼不同? –

+0

所以通常不建議在默認的執行上下文中使用冗長的計算。 –

+1

@DominykasMostauskis:不,默認執行上下文有一個線程池,並選擇其中的一個來運行未來的代碼。它不會每次跨越新線程,因爲創建線程是一項昂貴的操作,線程是有限的資源。 –

相關問題