使用Lightbend平臺(tái)的主要好處,包括Scala和Akka,它簡化了編寫并發(fā)軟件的過程。本文討論如何Lightbend平臺(tái),尤其是Akka,并發(fā)應(yīng)用程序共享內(nèi)存的方法。
Java內(nèi)存模型
在Java 5之前,Java內(nèi)存模型(JMM)有誤的定義。由多個(gè)線程訪問共享內(nèi)存時(shí)可以得到各種奇怪的結(jié)果,如:
- 線程看不到其它線程寫入的值:可見性問題;
- 線程觀察其他線程的“不可能”行為,指令沒有按預(yù)期的順序執(zhí)行引起:指令重新排序的問題。
Java 5的JSR 133解決了這些問題。JMM的一組規(guī)則是基于“發(fā)生前”的關(guān)系,這強(qiáng)制了當(dāng)一個(gè)內(nèi)存訪問發(fā)生在其它之前,相反地,允許在出現(xiàn)故障時(shí)發(fā)生。兩個(gè)例子說明這些規(guī)則: - 監(jiān)視鎖規(guī)則:釋放一個(gè)鎖之前每一個(gè)后繼得到相同的鎖。
- 易失變量規(guī)則:寫易失性變量之前每一個(gè)后續(xù)的讀相同的易失性變量。
盡管JMM看起來復(fù)雜,規(guī)范試圖找到一個(gè)易于使用和寫的能力之間的平衡性能,可伸縮的并發(fā)數(shù)據(jù)結(jié)構(gòu)。
Actor和Java內(nèi)存模型
Akka中Actor的實(shí)現(xiàn),有兩種方法在共享內(nèi)存環(huán)境下執(zhí)行多線程行為:
- 如果一個(gè)消息發(fā)送到一個(gè)Actor(如由另一個(gè)Actor)。在大多數(shù)情況下,消息是不可變的,但是如果這個(gè)信息構(gòu)造不可變的對象是不正確的,沒有“發(fā)生前”的規(guī)則,接收者可能部分初始化數(shù)據(jù)結(jié)構(gòu),甚至可能值是憑空捏造的(長型/雙精度型)
- 如果Actor在處理消息時(shí)改變內(nèi)部狀態(tài),并在片刻后訪問這個(gè)狀態(tài)在處理另一個(gè)消息時(shí)。深刻認(rèn)識(shí)到Actor模型中并不保證,同樣的線程會(huì)對不同消息執(zhí)行相同的Actor。
為了避免Actor的可見性和重排序問題,Akka保證了下例兩個(gè)“發(fā)生前”的規(guī)則: - Actor發(fā)送規(guī)則:一個(gè)Actor發(fā)送消息發(fā)生之前由通一個(gè)Actor接收消息。
- Actor后續(xù)處理規(guī)則:在處理消息發(fā)生前由同一個(gè)Actor處理后續(xù)消息。
注意
通俗的講改變Actor的內(nèi)部字段在下一個(gè)消息的可見。Actor中的字段不能是易失或相價(jià)的。
兩個(gè)規(guī)則僅適用于同一個(gè)Actor實(shí)例,對不同的Actor無效。
Futures和Java模型
完成Feature的“發(fā)生前”執(zhí)行調(diào)用的回調(diào)注冊。
我們建議不要封閉非final字段(在Java用final,在Scala中用val )如果你選擇封閉非final字段,它們必須被標(biāo)識(shí)為‘volatile’為了字段的當(dāng)前值是可見的回調(diào)。
如果封閉一個(gè)引用,需要確保實(shí)例是線程安全的。我們強(qiáng)烈建議遠(yuǎn)離使用鎖定的對象,因?yàn)樗谧顗牡那闆r下,引入性能問題和死鎖。這樣的同步是危險(xiǎn)的。
Actor和共享可變狀態(tài)
由于Akka運(yùn)行在JVM上仍有一些規(guī)則要遵循。
- 封閉Actor內(nèi)部狀態(tài),并向其它線程暴露它。
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.}
- 消息應(yīng)該是不可變的,這是為了避免共享可變狀態(tài)的陷阱。