現代服務是高度并發的—— 服務器通常是在10–100秒內并列上千個同時的操作——處理隱含的復雜性是創作健壯系統軟件的中心主題。
*線程*提供了一種表達并發的方式:它們給你獨立的,堆共享的(heap-sharing)由操作系統調度的執行上下文。然而,在Java里線程的創建是昂貴的,是一種必須托管的資源,通常借助于線程池。這對程序員創造了額外的復雜,也造成高度的耦合:很難從所使用的基礎資源中分離應用邏輯。
當創建高度分散(fan-out)的服務時這種復雜度尤其明顯: 每個輸入請求導致一大批對另一層系統的請求。在這些系統中,線程池必須被托管以便根據每一層請求的比例來平衡:一個線程池的管理不善會導致另一個線程池也出現問題。
一個健壯系統必須考慮超時和取消,兩者都需要引入更多“控制”線程,使問題更加復雜。注意若線程很廉價這些問題也將會被削弱:不再需要一個線程池,超時的線程將被丟棄,不再需要額外的資源管理。
因此,資源管理危害了模塊化。
### Future
使用Future管理并發。它們將并發操作從資源管理里解耦出來:例如,Finagle(譯注:twitter的一個RFC框架)以有效的方式在少量線程上實現并發操作的復用。Scala有一個輕量級的閉包字面語法(literal syntax),所以Futures引入了很少的語法開銷,它們成為很多程序員的第二本能。
Futures允許程序員用一種可擴充的,有處理失敗原則的聲明風格,來表達并發計算。這些特性使我們相信它們尤其適合在函數式編程中用,這也是鼓勵使用的風格。
*更愿意轉換(transforming)future而非自己創造*。Future的轉換(transformations)確保失敗會傳播,可以通過信號取消,對于程序員來說不必考慮Java內存模型的含義。甚至一個仔細的程序員會寫出下面的代碼,順序地發出10次RPC請求而后打印結果:
~~~
val p = new Promise[List[Result]]
var results: List[Result] = Nil
def collect() {
doRpc() onSuccess { result =>
results = result :: results
if (results.length < 10)
collect()
else
p.setValue(results)
} onFailure { t =>
p.setException(t)
}
}
collect()
p onSuccess { results =>
printf("Got results %s\n", results.mkString(", "))
}
~~~
程序員不得不確保RPC失敗是可傳播的,代碼散布在控制流程中;糟糕的是,代碼是錯誤的! 沒有聲明results是volatile,我們不能確保results每次迭代會保持前一次值。Java內存模型是一個狡猾的野獸,幸好我們可以通過用聲明式風格(declarative style)避開這些陷阱:
~~~
def collect(results: List[Result] = Nil): Future[List[Result]] =
doRpc() flatMap { result =>
if (results.length < 9)
collect(result :: results)
else
result :: results
}
collect() onSuccess { results =>
printf("Got results %s\n", results.mkString(", "))
}
~~~
我們用flatMap順序化操作,把我們處理中的結果預追加(prepend)到list中。這是一個通用的函數式編程習語的Futures譯本。這是正確的,不僅需要的樣板代碼(boilerplate)可以減少,易出錯的可能性也會減少,并且讀起來更好。
*Future組合子(combinators)的使用*。當操作多個futures時,Future.select,Future.join和Future.collect應該被組合編寫出通用模式。
### 集合
并發集合的主題充滿著意見、微妙(subtleties)、教條、恐懼/不確定/懷疑(FUD)。在大多實際場景都不存在問題:總是先用最簡單,最無聊,最標準的集合解決問題。 在你知道不能使用synchronized前不要去用一個并發集合:JVM有著老練的手段來使得同步開銷更小,所以它的效率能讓你驚訝。
如果一個不可變(immutable)集合可行,就盡可能用不可變集合——它們是指稱透明的(referentially transparent),所以在并發上下文推斷它們是簡單的。不可變集合的改變通常用更新引用到當前值(一個var單元或一個AtomicReference)。必須小心正確地應用:原子型的(atomics)必須重試(retried),變量(var類型的)必須聲明為volatile以保證它們發布(published)到它們的線程。
可變的并發集合有著復雜的語義,并利用Java內存模型的微妙的一面,所以在你使用前確定你理解它的含義——尤其對于發布更新(新的公開方法)。同步的集合同樣寫起來更好:像getOrElseUpdate操作不能夠被并發集合正確的實現,創建復合(composite)集合尤其容易出錯。