Java并发编程实战~Actor 模型
Hello Actor 模型
Actor 模型本質上是一種計算模型,基本的計算單元稱為 Actor,換言之,在 Actor 模型中,所有的計算都是在 Actor 中執行的。在面向對象編程里面,一切都是對象;在 Actor 模型里,一切都是 Actor,并且 Actor 之間是完全隔離的,不會共享任何變量。
當看到“不共享任何變量”的時候,相信你一定會眼前一亮,并發問題的根源就在于共享變量,而 Actor 模型中 Actor 之間不共享變量,那用 Actor 模型解決并發問題,一定是相當順手。的確是這樣,所以很多人就把 Actor 模型定義為一種并發計算模型。其實 Actor 模型早在 1973 年就被提出來了,只是直到最近幾年才被廣泛關注,一個主要原因就在于它是解決并發問題的利器,而最近幾年隨著多核處理器的發展,并發問題被推到了風口浪尖上。
但是 Java 語言本身并不支持 Actor 模型,所以如果你想在 Java 語言里使用 Actor 模型,就需要借助第三方類庫,目前能完備地支持 Actor 模型而且比較成熟的類庫就是 Akka 了。在詳細介紹 Actor 模型之前,我們就先基于 Akka 寫一個 Hello World 程序,讓你對 Actor 模型先有個感官的印象。
在下面的示例代碼中,我們首先創建了一個 ActorSystem(Actor 不能脫離 ActorSystem 存在);之后創建了一個 HelloActor,Akka 中創建 Actor 并不是 new 一個對象出來,而是通過調用 system.actorOf() 方法創建的,該方法返回的是 ActorRef,而不是 HelloActor;最后通過調用 ActorRef 的 tell() 方法給 HelloActor 發送了一條消息 “Actor” 。
//該Actor當收到消息message后, //會打印Hello message static class HelloActor extends UntypedActor {@Overridepublic void onReceive(Object message) {System.out.println("Hello " + message);} } public static void main(String[] args) {//創建Actor系統ActorSystem system = ActorSystem.create("HelloSystem");//創建HelloActorActorRef helloActor = system.actorOf(Props.create(HelloActor.class));//發送消息給HelloActorhelloActor.tell("Actor", ActorRef.noSender()); }通過這個例子,你會發現 Actor 模型和面向對象編程契合度非常高,完全可以用 Actor 類比面向對象編程里面的對象,而且 Actor 之間的通信方式完美地遵守了消息機制,而不是通過對象方法來實現對象之間的通信。那 Actor 中的消息機制和面向對象語言里的對象方法有什么區別呢?
消息和對象方法的區別
在沒有計算機的時代,異地的朋友往往是通過寫信來交流感情的,但信件發出去之后,也許會在寄送過程中弄丟了,也有可能寄到后,對方一直沒有時間寫回信……這個時候都可以讓郵局“背個鍋”,不過無論如何,也不過是重寫一封,生活繼續。
Actor 中的消息機制,就可以類比這現實世界里的寫信。Actor 內部有一個郵箱(Mailbox),接收到的消息都是先放到郵箱里,如果郵箱里有積壓的消息,那么新收到的消息就不會馬上得到處理,也正是因為 Actor 使用單線程處理消息,所以不會出現并發問題。你可以把 Actor 內部的工作模式想象成只有一個消費者線程的生產者 - 消費者模式。
所以,在 Actor 模型里,發送消息僅僅是把消息發出去而已,接收消息的 Actor 在接收到消息后,也不一定會立即處理,也就是說 Actor 中的消息機制完全是異步的。而調用對象方法,實際上是同步的,對象方法 return 之前,調用方會一直等待。
除此之外,調用對象方法,需要持有對象的引用,所有的對象必須在同一個進程中。而在 Actor 中發送消息,類似于現實中的寫信,只需要知道對方的地址就可以,發送消息和接收消息的 Actor 可以不在一個進程中,也可以不在同一臺機器上。因此,Actor 模型不但適用于并發計算,還適用于分布式計算。
Actor 的規范化定義
通過上面的介紹,相信你應該已經對 Actor 有一個感官印象了,下面我們再來看看 Actor 規范化的定義是什么樣的。Actor 是一種基礎的計算單元,具體來講包括三部分能力,分別是:
- 處理能力,處理接收到的消息。
- 存儲能力,Actor 可以存儲自己的內部狀態,并且內部狀態在不同 Actor 之間是絕對隔離的。
- 通信能力,Actor 可以和其他 Actor 之間通信。
當一個 Actor 接收的一條消息之后,這個 Actor 可以做以下三件事:
- 創建更多的 Actor;
- 發消息給其他 Actor;
- 確定如何處理下一條消息。
其中前兩條還是很好理解的,就是最后一條,該如何去理解呢?前面我們說過 Actor 具備存儲能力,它有自己的內部狀態,所以你也可以把 Actor 看作一個狀態機,把 Actor 處理消息看作是觸發狀態機的狀態變化;而狀態機的變化往往要基于上一個狀態,觸發狀態機發生變化的時刻,上一個狀態必須是確定的,所以確定如何處理下一條消息,本質上不過是改變內部狀態。
在多線程里面,由于可能存在競態條件,所以根據當前狀態確定如何處理下一條消息還是有難度的,需要使用各種同步工具,但在 Actor 模型里,由于是單線程處理,所以就不存在競態條件問題了。
用 Actor 實現累加器
支持并發的累加器可能是最簡單并且有代表性的并發問題了,可以基于互斥鎖方案實現,也可以基于原子類實現,但今天我們要嘗試用 Actor 來實現。
在下面的示例代碼中,CounterActor 內部持有累計值 counter,當 CounterActor 接收到一個數值型的消息 message 時,就將累計值 counter += message;但如果是其他類型的消息,則打印當前累計值 counter。在 main() 方法中,我們啟動了 4 個線程來執行累加操作。整個程序沒有鎖,也沒有 CAS,但是程序是線程安全的。
//累加器 static class CounterActor extends UntypedActor {private int counter = 0;@Overridepublic void onReceive(Object message){//如果接收到的消息是數字類型,執行累加操作,//否則打印counter的值if (message instanceof Number) {counter += ((Number) message).intValue();} else {System.out.println(counter);}} } public static void main(String[] args) throws InterruptedException {//創建Actor系統ActorSystem system = ActorSystem.create("HelloSystem");//4個線程生產消息ExecutorService es = Executors.newFixedThreadPool(4);//創建CounterActor ActorRef counterActor = system.actorOf(Props.create(CounterActor.class));//生產4*100000個消息 for (int i=0; i<4; i++) {es.execute(()->{for (int j=0; j<100000; j++) {counterActor.tell(1, ActorRef.noSender());}});}//關閉線程池es.shutdown();//等待CounterActor處理完所有消息Thread.sleep(1000);//打印結果counterActor.tell("", ActorRef.noSender());//關閉Actor系統system.shutdown(); }總結
Actor 模型是一種非常簡單的計算模型,其中 Actor 是最基本的計算單元,Actor 之間是通過消息進行通信。Actor 與面向對象編程(OOP)中的對象匹配度非常高,在面向對象編程里,系統由類似于生物細胞那樣的對象構成,對象之間也是通過消息進行通信,所以在面向對象語言里使用 Actor 模型基本上不會有違和感。
在 Java 領域,除了可以使用 Akka 來支持 Actor 模型外,還可以使用 Vert.x,不過相對來說 Vert.x 更像是 Actor 模型的隱式實現,對應關系不像 Akka 那樣明顯,不過本質上也是一種 Actor 模型。
Actor 可以創建新的 Actor,這些 Actor 最終會呈現出一個樹狀結構,非常像現實世界里的組織結構,所以利用 Actor 模型來對程序進行建模,和現實世界的匹配度非常高。Actor 模型和現實世界一樣都是異步模型,理論上不保證消息百分百送達,也不保證消息送達的順序和發送的順序是一致的,甚至無法保證消息會被百分百處理。雖然實現 Actor 模型的廠商都在試圖解決這些問題,但遺憾的是解決得并不完美,所以使用 Actor 模型也是有成本的。
?
總結
以上是生活随笔為你收集整理的Java并发编程实战~Actor 模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java并发编程实战~final
- 下一篇: java美元兑换,(Java实现) 美元