这两个future都是为了做并发操作,它们要取代的是你手动起一个thread。future的主要好处就是简单,只要遵循一些基本的规则,你就很容易做出问题很少的高并发应用。(这点有点像go的协程,但我觉得future因为限制更多一些,对异常的处理也更好,所以实际使用中表现的更稳健)。我个人建议是scala的并发编程要尽量使用future或者是使用对future的进一步封装。
scala的future有标准实现,也有twitter.util的实现,也有其他实现,这些在Scala世界里都很常见,这些future一般类型并不兼容,但基本元素是差不多的,可以通过代码互相转换,但这个转换还是有点麻烦,而且我们一般需要同种的future组成一个future chain,所以一般我们在一个项目里,往往会就固定使用一种future,而我twitter实现用的比较多, 我下面的回答主要针对twitter future。
要说future并不是一个pure fp的东西,我日常见到的future很多都不完全符合monads的那几条law。future本身是欢迎在自己block里写effectful的东西的,我们其实是经常用future做一些state change或者修改什么东西,比如说你读写个数据库,写个缓存什么,lazy load一个什么东西。当然,你也可以做得更好,让future完全符合monads,或者你也可以看看scalaz的Task,我不是scalaz的粉,就不多说了。
这个东西到底有多简单,比如说对于一个远程服务,不管是客户端还是服务器端,都可以定义成
type Service[Req, Rep] = Req => Future[Rep]
这样一个定义就可以覆盖远程Rpc call或者是个disk IO,我们就是期待一个可能还没有或者可能已经有了的一个Rep值。
比如说客户端就可以这样写
val client: Service[HttpReq, HttpRep] = xxxx;
client(HttpReq("/someapi")).map{
rep =>
println(rep.code)
}
我们可以让future帮我们拿着一个主/调用线程可能还没有的值,随便怎么玩,可以转换,可以组合。比如说大家猜上面这个client.map的返回值是个什么类型。。。
我们用future有以下几个目的:希望操作不block主线程,后端服务一旦形成进程/线程阻塞,后果非常可怕,这是我们当年用future最主要的目的。
希望轻量级启动一个异步操作,想减少上下文的切换等等。因为我见过的future的实现其实背后是个线程池或者什么类似的东西(scala里喜欢叫ExecutionContext),不会真的每次启动一个线程,这有点像go中的协程,会对高并发很有利。但这就有一个问题,future执行的时候能否被抢断?如果不能,你在 future里做一个blocking的操作,不是照样会阻塞线程池吗?当然,实践中这种问题还是好解决的,要么就不在future中做任何blocking IO操作,要么就为blocking的操作另起一个ExecutionContext。
希望异步操作可以很容易chain或者组合到一起。你可以用flatMap把几个的future连在一起,就和写同步操作的代码一样,这是很方便的。如果你的代码里可以把每一个Future都像Option/Either/Try一样可以被当作一个value来对待,那你的代码就会是很强壮的,你也更容易写出一些bug free的代码。
希望异常可以在future chain中得到传递, 也可以比较好从异常中恢复。我想后端程序员都有在新thread里扔异常的经历,你要是想让你的异步操作中的异常能在主线程中得到处理,你还真是得费点事,不过这个在future里就比较简单了。(如果你是用finagle的话,这是twiter future的亮点,各种retry,fallback等等,都很容易搞)
java completableFuture的接口略微差了一些,我没怎么用过,但本质应该是一样的