1.Actor模型
在使用Java進行並發編程時需要特別的關注鎖和內存原子性等一系列線程問題,而Actor模型內部的狀態由它自己維護即它內部數據只能由它自己修改(通過消息傳遞來進行狀態修改),所以使用Actors模型進行並發編程可以很好地避免這些問題,Actor由狀態(state)、行為(Behavior)和郵箱(mailBox)三部分組成
- 狀態(state):Actor中的狀態指的是Actor對象的變量信息,狀態由Actor自己管理,避免了並發環境下的鎖和內存原子性等問題
- 行為(Behavior):行為指定的是Actor中計算邏輯,通過Actor接收到消息來改變Actor的狀態
- 郵箱(mailBox):郵箱是Actor和Actor之間的通信橋梁,郵箱內部通過FIFO消息隊列來存儲發送方Actor消息,接受方Actor從郵箱隊列中獲取消息
Actor的基礎就是消息傳遞
2.使用Actor模型的好處:
- 事件模型驅動--Actor之間的通信是異步的,即使Actor在發送消息后也無需阻塞或者等待就能夠處理其他事情
- 強隔離性--Actor中的方法不能由外部直接調用,所有的一切都通過消息傳遞進行的,從而避免了Actor之間的數據共享,想要
觀察到另一個Actor的狀態變化只能通過消息傳遞進行詢問 - 位置透明--無論Actor地址是在本地還是在遠程機上對於代碼來說都是一樣的
- 輕量性--Actor是非常輕量的計算單機,單個Actor僅占400多字節,只需少量內存就能達到高並發
3.Actor模型原理
以下通過學生與教師之間的郵件通信來理解akka中的Actor模型
學生-教師的消息傳遞
首先先只考慮學生單向發送消息給教師(學生--->教師),如下圖:
圖解:
- 學生創建一個ActorSystem
- 通過ActorSystem創建ActorRef,將QuoteRequest消息發送到ActorRef(教師代理)
- ActorRef(教師代理)消息傳遞到Dispatcher中
- Dispatcher依次的將消息發送到TeacherActor的郵箱中
- Dispatcher將郵箱推送到一條線程中
- 郵箱取出一條消息並委派給TeacherActor的receive方法
下面再詳細的解釋每一步驟
StudentSimulatorApp主程序詳解:
首先StudentSimulatorApp會先啟動JVM並初始化ActorSystem
如上圖所示,StudentSimulatorApp的主要工作為:
-
創建ActorSystem
- ActorSystem作為頂級Actor,可以創建和停止Actors,甚至可關閉整個Actor環境,
此外Actors是按層次划分的,ActorSystem就好比Java中的Object對象,Scala中的Any,
是所有Actors的根,當你通過ActorSystem的actof方法創建Actor時,實際就是在ActorSystem
下創建了一個子Actor。
可通過以下代碼來初始化ActorSystem
val system = ActorSystem("UniversityMessageSystem")
- ActorSystem作為頂級Actor,可以創建和停止Actors,甚至可關閉整個Actor環境,
-
通過ActorSystem創建TeacherActor的代理(ActorRef)
- 看看TeacherActor的代理的創建代碼
val teacherActorRef:ActorRef = system.actorOf(Props[TeacherActor])
ActorSystem通過actorOf創建Actor,但其並不返回TeacherActor而是返
回一個類型為ActorRef的東西。
ActorRef作為Actor的代理,使得客戶端並不直接與Actor對話,這種Actor
模型也是為了避免TeacherActor的自定義/私有方法或變量被直接訪問,所
以你最好將消息發送給ActorRef,由它去傳遞給目標Actor -
發送QuoteRequest消息到代理中
- 你只需通過!方法將QuoteReques消息發送給ActorRef(注意:ActorRef也有個tell方法,其作用就委托回調給!)
techerActorRef!QuoteRequest
等價於teacherActorRef.tell(QuoteRequest, teacherActorRef)
- 你只需通過!方法將QuoteReques消息發送給ActorRef(注意:ActorRef也有個tell方法,其作用就委托回調給!)
完整StudentSimulatorApp代碼
object StudentSimulatorApp extends App{
//初始化ActorSystem
val actorSystem=ActorSystem("UniversityMessageSystem")
//構建teacherActorRef
val teacherActorRef=actorSystem.actorOf(Props[TeacherActor])
//發送消息給TeacherActor
teacherActorRef! QuoteRequest
Thread.sleep (2000)
//關閉 ActorSystem,如果不關閉JVM將不會退出
actorSystem.shutdown()
}
QuoteRequest類
object TeacherProtocol{
case class QuoteRequest() //請求
case class QuoteResponse(quoteString:String) //響應
}
Dispatcher和MailBox
ActorRef將消息處理能力委派給Dispatcher,實際上,當我們創建ActorSystem和ActorRef時,
Dispatcher和MailBox就已經被創建了
-
MailBox
- 每個Actor都有一個MailBox,同樣,Teacher也有個MailBox,其會檢查MailBox並處理消息。
MailBox內部采用的是FIFO隊列來存儲消息,有一點不同的是,現實中我們的最新郵件
會在郵箱的最前面。
- 每個Actor都有一個MailBox,同樣,Teacher也有個MailBox,其會檢查MailBox並處理消息。
-
Dispatcher
-
Dispatcher從ActorRef中獲取消息並傳遞給MailBox,Dispatcher封裝了一個線程池,之后在
線程池中執行MailBox。protected[akka] override def registerForExecution(mbox: Mailbox, ...): Boolean = {
...
try {
executorService execute mbox
...
} -
為什么能執行MailBox?
- 看看MailBox的實現,沒錯,其實現了Runnable接口
private[akka] abstract class Mailbox(val messageQueue: MessageQueue) extends SystemMessageQueue with Runnable
- 看看MailBox的實現,沒錯,其實現了Runnable接口
-
TeacherActor
- 當ActorRef發送消息調用目標Actor的reveive方法時,MailBox中的run方法被執行,接着從消息隊列中取出一條消息並傳遞給Actor處理
class TeacherActor extends Actor {
val quotes = List(
"Moderation is for cowards",
"Anything worth doing is worth overdoing",
"The trouble is you think you have time",
"You never gonna know if you never even try")
def receive = {
case QuoteRequest => {
import util.Random
//從list中隨機選出一條消息作為回應(這里只print並沒回應學生的請求)
val quoteResponse=QuoteResponse(quotes(Random.nextInt(quotes.size)))
println (quoteResponse)
}
}
}
TeacherActor的receive方法將匹配QuoteRequest消息
這里有個國外博主寫的Akka系列博客都很贊,分享給大家!
本文參考資料:
AKKA NOTES - ACTOR MESSAGING - 1
Akka框架——第一節:並發編程簡介
Akka Quickstart with Scala