使用Lightbend平臺的主要好處,包括Scala和Akka,它簡化了編寫并發軟件的過程。本文討論如何Lightbend平臺,尤其是Akka,并發應用程序共享內存的方法。
Java內存模型
在Java 5之前,Java內存模型(JMM)有誤的定義。由多個線程訪問共享內存時可以得到各種奇怪的結果,如:
- 線程看不到其它線程寫入的值:可見性問題;
- 線程觀察其他線程的“不可能”行為,指令沒有按預期的順序執行引起:指令重新排序的問題。
Java 5的JSR 133解決了這些問題。JMM的一組規則是基于“發生前”的關系,這強制了當一個內存訪問發生在其它之前,相反地,允許在出現故障時發生。兩個例子說明這些規則: - 監視鎖規則:釋放一個鎖之前每一個后繼得到相同的鎖。
- 易失變量規則:寫易失性變量之前每一個后續的讀相同的易失性變量。
盡管JMM看起來復雜,規范試圖找到一個易于使用和寫的能力之間的平衡性能,可伸縮的并發數據結構。
Actor和Java內存模型
Akka中Actor的實現,有兩種方法在共享內存環境下執行多線程行為:
- 如果一個消息發送到一個Actor(如由另一個Actor)。在大多數情況下,消息是不可變的,但是如果這個信息構造不可變的對象是不正確的,沒有“發生前”的規則,接收者可能部分初始化數據結構,甚至可能值是憑空捏造的(長型/雙精度型)
- 如果Actor在處理消息時改變內部狀態,并在片刻后訪問這個狀態在處理另一個消息時。深刻認識到Actor模型中并不保證,同樣的線程會對不同消息執行相同的Actor。
為了避免Actor的可見性和重排序問題,Akka保證了下例兩個“發生前”的規則: - Actor發送規則:一個Actor發送消息發生之前由通一個Actor接收消息。
- Actor后續處理規則:在處理消息發生前由同一個Actor處理后續消息。
注意
通俗的講改變Actor的內部字段在下一個消息的可見。Actor中的字段不能是易失或相價的。
兩個規則僅適用于同一個Actor實例,對不同的Actor無效。
Futures和Java模型
完成Feature的“發生前”執行調用的回調注冊。
我們建議不要封閉非final字段(在Java用final,在Scala中用val )如果你選擇封閉非final字段,它們必須被標識為‘volatile’為了字段的當前值是可見的回調。
如果封閉一個引用,需要確保實例是線程安全的。我們強烈建議遠離使用鎖定的對象,因為它在最壞的情況下,引入性能問題和死鎖。這樣的同步是危險的。
Actor和共享可變狀態
由于Akka運行在JVM上仍有一些規則要遵循。
- 封閉Actor內部狀態,并向其它線程暴露它。
1. class MyActor extends Actor {
2. var state = ...
3. def receive = {
4. case _ =>
5. //Wrongs
6.
7. // Very bad, shared mutable state,
8. // will break your application in weird ways
9. Future { state = NewState }
10. anotherActor ? message onSuccess { r => state = r }
11.
12. // Very bad, "sender" changes for every message,
13. // shared mutable state bug
14. Future { expensiveCalculation(sender()) }
15.
16. //Rights
17.
18. // Completely safe, "self" is OK to close over
19. // and it's an ActorRef, which is thread-safe
20. Future { expensiveCalculation() } onComplete { f => self ! f.value.get }
21.
22. // Completely safe, we close over a fixed value
23. // and it's an ActorRef, which is thread-safe
24. val currentSender = sender()
25. Future { expensiveCalculation(currentSender) }
26. }
27.}
- 消息應該是不可變的,這是為了避免共享可變狀態的陷阱。