1. 程式人生 > >Akka與Java記憶體模型的關係

Akka與Java記憶體模型的關係

不管你使用的Typesafe系統是Scala版本還是Java版本,都可以使你編寫併發程式的過程變得更加容易。這篇文章主要討論的是Typesafe系統,特別是針對Akka在併發程式中對共享記憶體的處理部分。

Java記憶體模型

在之前的Java 5 版本中,Java記憶體模型的定義是很值得商榷的。以至於在共享記憶體環境下的多執行緒處理的結果變得多種多樣,比如:

  1. 執行緒讀取不到其他執行緒寫入的值:記憶體可見性問題
  2. 執行緒得到了其他執行緒的“非正常”行為,這也是因為底層指令沒有按照期望的順序執行的結果:指令重排序問題

Java 5版本的JSR 133規範其實已經解決了很多這樣的問題。首先Java記憶體模型是以先序執行原則為前提的規則集合,也就是說它限制的結果是記憶體的訪問必須是依次進行,可是它同時又允許上述的行為亂序的執行。下面是一些規則的例子:

  1. 監控鎖規則:在同一把鎖的獲取之前必須已經進行了鎖的釋放
  2. Volatile變數規則:對同一個volatile變數的讀取必須在對其寫入之後

儘管Java記憶體模型表面上看很複雜,但是在規範的制定上一直在尋求高可用性以及使其具有可編寫高效能、擴充套件性良好的併發程式結構。

角色(Actor)和Java記憶體模型

針對Akka中的角色(Actor)實現,多執行緒可以有兩種方式來對共享記憶體進行操作:

  1. 如果訊息被髮送給一個角色(Actor)。在大多數情況下訊息是不可變的,但是也有可能訊息並不是如理想中的那樣完全不可變化,如果沒有先序執行規則的保證,接受訊息的角色(Actor)有可能只會看到初始化不完全的資料或者更嚴重的根本看不到。
  2. 如果角色(Actor)通過訊息處理改變了內部狀態,然後處理其他的訊息時又獲取內部狀態。在這種情況下你需要注意的是角色(Actor)模型並不能保證同一執行緒可以對不同的訊息用相同的角色(Actor)處理。

為了避免角色(Actor)之間的可見性和重排序問題,Akka提供了下面兩個先序執行的原則來保證:

  1. 角色(Actor)傳送規則:傳送訊息給一個角色(Actor)的操作必須在同一個角色(Actor)接受了那條訊息之前發生。
  2. 角色(Actor)後續處理規則:同一角色(Actor)的訊息處理必須是序列的。

注意:通常意義下也就是當下一訊息被角色(Actor)處理的時候,角色(Actor)內部欄位的變化在當前是可見的。因此角色(Actor)中的欄位不需要被宣告為volatile,也不需要是等效的。

上述兩個原則只對同一角色(Actor)例項有效,如果使用的是不同的角色(Actor)則沒有任何作用。

Future和Java記憶體模型

註冊在Future上的所有回撥必須在Future操作完成之後執行。

建議多使用final變數,如果使用了過多的非final變數也必須同時被標識為volatile以便可以在回撥中使用欄位的最新值。

如果使用過多引用型別,這種情況下必須保證引用指向的例項是執行緒安全的。我們強烈建議不要使用物件鎖,因為它可能造成效能低下,並且最嚴重的是可能導致死鎖的產生。這其實也是使用synchronized的危險所在。

軟體事務記憶體(STM)和Java記憶體模型

Akka的軟體事務記憶體也支援先序執行規則:

事務引用規則:針對同一事務引用的成功寫的提交操作必須發生在同一事務引用的後續讀操作之前。

這個規則看起來和Java記憶體模型中的volatile變數很相似。當前版本的Akka軟體事務模型支援延遲寫入,因此對於共享記憶體的實際的寫操作只有在事務提交的時候才會執行。事務中的所有寫操作都會被置於本地緩衝並對其他事務不可見的,這就保證了不會發生髒讀現象。

在Akka中這些規則到底是怎樣的這都是實現細節而且都是一直在變動的,具體的下班取決於使用的配置。但是不管怎樣他們都是構建在比如監控鎖規則或者volatile變數規則這些Java虛擬機器模型規則之上的。這就是說,如果你使用Akka,是不用新增同步來保證先序執行的關係的,因為這些Akka已經為你做了。你只需要專注於業務邏輯即可,Akka框架會替你做好這些規則的保證。

角色(Actor)和共享可變狀態

但是畢竟Akka是執行在Java虛擬機器上的,當然還是要遵守一些規則的。

使用內部角色(Actor)狀態並且將它暴露給其他執行緒

class MyActor extends Actor{
    var state=...
    def receive ={
        case _=>
           //錯誤
           //相當的糟糕,共享可變狀態
           //會使你的應用出現奇怪的行為
           Future { state = NewState }
           anotherActor ? message onSuccess {r => state = r }

           //相當糟糕,“sender”會因為每條訊息而改變
           //共享可變狀態BUG
           Future {expensiveCalculation(sender())}

           //正確
           //完全安全,“self”可以封裝
           Future {expensiveCalculation() } onComplete { f=> self ! f.value.get }

           //完全安全,我們封裝一個固定值
           //而且它是一個ActorRef,這個物件是執行緒安全的
           val currentSender = sender()
           Future {expensiveCalculation(currentSender)}
    }
}

訊息必須是不可變的,這樣可以避開共享可變狀態的陷阱。