Akka學習筆記(五):Akka與Java的內存模型
Akka學習筆記(五):Akka與Java的內存模型
Akka簡化了編寫並發軟件的過程,本文主要討論Akka如何在並發應用中訪問共享內存。
Java內存模型
Java5之前的JMM是相當混亂的。多線程訪問共享內存很有可能會得奇怪的結果,如:
- 可見性問題,無法及時看到其他線程寫入的值
- 指令亂序,觀測到其他線程不可能的行為
從Java 5的JSR 133的實現,很多問題就解決了。JMM是基於一組"happens-before"關聯規則,限製了訪問內存的行為必須在另一個內存訪問行為之前發生。當不想按順序發生時,可以使用:
- 監視器鎖規則:對一個鎖的釋放先於所有後續對同一個鎖的獲取
- volatile變量規則:對一個volatile變量的寫操作先於所有後續對同一個volatile變量的讀
JMM看起來很複雜,但是其規範試圖在編寫高性能,並發數據結構的能力間尋找平衡
Actor與Java內存模型
使用Akka的Actor,有兩種方法可以使多線程操作共享內存:
- 假如一個message被發送給一個actor,在大多數情況下,message是不可變對象,萬一message不是不可變的,沒有”happen before"規則,receiver可能會看到部分初始化的數據,甚至可能看到無中生有的數據(long/double)
- 如果一個actor在處理消息時,改變了自己的內部狀態,而後又在處理其他消息的時候訪問了這個狀態。我們需要知道的是,在使用Actor模型時,無法保證同一個線程在處理不同消息時,使用同一個actor(是指一個線程中有多個actor?還是一個actor改變了自己內部的狀態後,就不是同一個actor?)
為了防止actor出現可見性問題,執行順序問題,Akka製定了如下"happen before"規則:
- 發送規則:一條消息的發送動作先於同一個actor對同一條消息的接收
- actor後續處理規則:一條消息的處理,優先於同一個actor的下一條消息
兩條規則隻對同一個actor實例有效
通俗的解釋:actor的內部變量(internal fields)是可見的,當下一個消息準備被處理時。所以你的actor的內部變量必須是volatile或者equivalent。
Futures與Java內存模型
一個Future的完成 “先於” 任何注冊到它的回調函數的執行。
我們建議不要捕捉(close over)非final的值 (Java中稱final,Scala中稱val), 如果你 一定 要捕捉非final的值, 它們必須被標記為 volatile 來讓它的當前值對回調代碼可見。
如果你捕捉一個引用,, 你還必須保證它所指代的實例是線程安全的。 我們強烈建議遠離使用鎖的對象,因為它們會引入性能問題,甚至可能造成死鎖。 這些是使用synchronized的風險。
STM與Java內存模型
Akka中的軟件事務性內存 (STM) 也提供了一條 “發生在先” 規則:
- 事務性引用規則: 在提交過程中對一個事務性引用的成功的寫操作先於所有對同一事務性引用的後續讀操作發生。
這條規則非常象JMM中的 ‘volatile 變量’ 規則. 目前Akka STM隻支持延遲寫,所以對共享內存的實際寫操作會被延遲到事務提交之時。事務中的寫操作被存放在一個本地緩衝區中 (事務的寫操作集) ,對其它的事務是不可見的。這就是為什麼髒讀是不可能的。
這些規則在Akka中的實現會隨時間而變化,精確的細節甚至可能依賴於所使用的配置。但是它們是建立在其它的JMM規則如監視器鎖規則和volatile變量規則基礎上的。 這意味著Akka用戶不需要操心為了提供“發生先於”關係而增加同步,因為這是Akka的工作。這樣你可以騰出手來處理你的業務邏輯,讓Akka框架來保證這些規則的滿足。
Actors與共享的可變狀態
由於Akka運行在JVM,有些規則仍然必須遵守
-
捕捉actor內部狀態並暴露給其他線程
class MyActor extends Actor { var state = ... def receive = { case _ => //錯誤示範 // Very bad, 共享可變狀態, // will break your application in weird ways Future { state = NewState } anotherActor ? message onSuccess { r => state = r } // Very bad, "sender" 隨每個消息改變, //共享可變狀態 bug Future { expensiveCalculation(sender()) } //正確示範 // Completely safe, "self" is OK to close over // and it's an ActorRef, which is thread-safe Future { expensiveCalculation() } onComplete { f => self ! f.value.get } // 非常安全,我們捕捉了一個固定值 // 並且它是一個Actor引用,是線程安全的 val currentSender = sender() Future { expensiveCalculation(currentSender) } } }
- 消息應該是不可變的,為了避免共享可變狀態的陷阱
最後更新:2017-04-03 05:39:44