在上一遍博客中,我们已经分析了actor创建的大致过程,但只是涉及到了Dipatcher/Mailbox/ActorCell/InternalActorRef等对象的创建,并没有介绍我们自定义的继承Actor特质的类如何完成初始化。这篇文章对这一部分内容进行简单的补充。
在akka.actor.dungeon.init代码中,有一段代码我们当时没有分析,此处对此代码进行深入分析,然后才能找到Actor子类完成创建的真实过程。

上面是init的代码片段,其中有一个局部变量createMessage,根据前后分析,它的值应该是Create这个case
class。最后mailbox.systemEnqueue(self,
createMessage)这个代码给actor对应的邮箱发送了该消息。

/**
* INTERNAL API
*/
@SerialVersionUID(1L)
private[akka] final case class Create(failure: Option[ActorInitializationException]) extends SystemMessage // sent to self from Dispatcher.register

根据Create类名以及前后上下文分析,这应该是指示Actor完成初始化的。那么我们要分析一下actor是如何对该消息响应的。那么究竟是哪段代码对这个消息进行响应的呢?
如果读过之前的文章,你肯定能想起来Mailbox在循环处理消息时,有一个processAllSystemMessages方法,这个方法里面调用了actor的systemInvoke方法。具体源码如下:

/**
* Will at least try to process all queued system messages: in case of
* failure simply drop and go on to the next, because there is nothing to
* restart here (failure is in ActorCell somewhere …). In case the mailbox
* becomes closed (because of processing a Terminate message), dump all
* already dequeued message to deadLetters.
*/
final def processAllSystemMessages() {
var interruption: Throwable = null
var messageList = systemDrain(SystemMessageList.LNil)
while ((messageList.nonEmpty) && !isClosed) {
val msg = messageList.head
messageList = messageList.tail
msg.unlink()
if (debug) println(actor.self + " processing system message " + msg + " with " + actor.childrenRefs)
// we know here that systemInvoke ensures that only "fatal" exceptions get rethrown
actor systemInvoke msg
if (Thread.interrupted())
interruption = new InterruptedException("Interrupted while processing system messages")
// don’t ever execute normal message when system message present!
if ((messageList.isEmpty) && !isClosed) messageList = systemDrain(SystemMessageList.LNil)
}
/*
* if we closed the mailbox, we must dump the remaining system messages
* to deadLetters (this is essential for DeathWatch)
*/
// 忽略剩余源码
}

我们来研究一下systemInvoke的代码

/*
* MESSAGE PROCESSING
*/
//Memory consistency is handled by the Mailbox (reading mailbox status then processing messages, then writing mailbox status
final def systemInvoke(message: SystemMessage): Unit = {
/*
* When recreate/suspend/resume are received while restarting (i.e. between
* preRestart and postRestart, waiting for children to terminate), these
* must not be executed immediately, but instead queued and released after
* finishRecreate returns. This can only ever be triggered by
* ChildTerminated, and ChildTerminated is not one of the queued message
* types (hence the overwrite further down). Mailbox sets message.next=null
* before systemInvoke, so this will only be non-null during such a replay.
*/ def calculateState: Int =
if (waitingForChildrenOrNull ne null) SuspendedWaitForChildrenState
else if (mailbox.isSuspended) SuspendedState
else DefaultState @tailrec def sendAllToDeadLetters(messages: EarliestFirstSystemMessageList): Unit =
if (messages.nonEmpty) {
val tail = messages.tail
val msg = messages.head
msg.unlink()
provider.deadLetters ! msg
sendAllToDeadLetters(tail)
} def shouldStash(m: SystemMessage, state: Int): Boolean =
(state: @switch) match {
case DefaultState ⇒ false
case SuspendedState ⇒ m.isInstanceOf[StashWhenFailed]
case SuspendedWaitForChildrenState ⇒ m.isInstanceOf[StashWhenWaitingForChildren]
} @tailrec
def invokeAll(messages: EarliestFirstSystemMessageList, currentState: Int): Unit = {
val rest = messages.tail
val message = messages.head
message.unlink()
try {
message match {
case message: SystemMessage if shouldStash(message, currentState) ⇒ stash(message)
case f: Failed ⇒ handleFailure(f)
case DeathWatchNotification(a, ec, at) ⇒ watchedActorTerminated(a, ec, at)
case Create(failure) ⇒ create(failure)
case Watch(watchee, watcher) ⇒ addWatcher(watchee, watcher)
case Unwatch(watchee, watcher) ⇒ remWatcher(watchee, watcher)
case Recreate(cause) ⇒ faultRecreate(cause)
case Suspend() ⇒ faultSuspend()
case Resume(inRespToFailure) ⇒ faultResume(inRespToFailure)
case Terminate() ⇒ terminate()
case Supervise(child, async) ⇒ supervise(child, async)
case NoMessage ⇒ // only here to suppress warning
}
} catch handleNonFatalOrInterruptedException { e ⇒
handleInvokeFailure(Nil, e)
}
val newState = calculateState
// As each state accepts a strict subset of another state, it is enough to unstash if we "walk up" the state
// chain
val todo = if (newState < currentState) unstashAll() reverse_::: rest else rest if (isTerminated) sendAllToDeadLetters(todo)
else if (todo.nonEmpty) invokeAll(todo, newState)
} invokeAll(new EarliestFirstSystemMessageList(message), calculateState)
}

由于我们只是准备分析actor的创建过程,所以上面的代码,我们只关注对Create消息的处理:create(failure)。也就是说调用了create函数。

protected def create(failure: Option[ActorInitializationException]): Unit = {

    def clearOutActorIfNonNull(): Unit = {
if (actor != null) {
clearActorFields(actor, recreate = false)
actor = null // ensure that we know that we failed during creation
}
} failure.foreach { throw _ } try {
val created = newActor()
actor = created
created.aroundPreStart()
checkReceiveTimeout
if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(created), "started (" + created + ")"))
} catch {
case e: InterruptedException ⇒
clearOutActorIfNonNull()
Thread.currentThread().interrupt()
throw ActorInitializationException(self, "interruption during creation", e)
case NonFatal(e) ⇒
clearOutActorIfNonNull()
e match {
case i: InstantiationException ⇒ throw ActorInitializationException(
self,
"""exception during creation, this problem is likely to occur because the class of the Actor you tried to create is either,
a non-static inner class (in which case make it a static inner class or use Props(new ...) or Props( new Creator ... )
or is missing an appropriate, reachable no-args constructor.
""", i.getCause)
case x ⇒ throw ActorInitializationException(self, "exception during creation", x)
}
}
}

我们来分析一下这个create函数。其中主要的逻辑都在try中,首先调用newActor函数,创建了Actor实例,然后赋值给actor字段。actor字段我们已经知道,这是ActorCell的最终actor实例。

/*
* ACTOR INSTANCE HANDLING
*/ //This method is in charge of setting up the contextStack and create a new instance of the Actor
protected def newActor(): Actor = {
contextStack.set(this :: contextStack.get)
try {
behaviorStack = emptyBehaviorStack
val instance = props.newActor() if (instance eq null)
throw ActorInitializationException(self, "Actor instance passed to actorOf can't be 'null'") // If no becomes were issued, the actors behavior is its receive method
behaviorStack = if (behaviorStack.isEmpty) instance.receive :: behaviorStack else behaviorStack
instance
} finally {
val stackAfter = contextStack.get
if (stackAfter.nonEmpty)
contextStack.set(if (stackAfter.head eq null) stackAfter.tail.tail else stackAfter.tail) // pop null marker plus our context
}
}

newActor函数源码如上,抛去其他代码,该函数调用了props.newActor创建了最终的Actor实例,也就是我们自定义的Actor子类。通过源码注释我们知道behaviorStack是actor当前行为的一个栈。如果读者用过become的话,对这段代码应该比较好理解。我们在actor内部使用become方法改变当前actor实例的时候,其实是把新的receive函数压入栈顶,mailbox在调用receive时,其实是取出当前栈顶的receive函数进行处理的。当然这是akka以前版本的默认行为。为什么这样说呢?因为新版本默认行为就是简单的把最新的receive函数替换旧receive函数,如果想恢复旧receive函数,需要开发者在编码时,再次调用become用旧receive函数替换当前receive。为什么要这么做?当然是为了防止开发者恶意或者无意中胡乱调用become,造成栈溢出喽。
props.newActor我们不再深入分析,这应该就是通过反射创建Actor特质的子类,也就是我们自定义的actor。
至此,我们自定义的actor就真正完成了初始化。细心的读者一定会发现,就连actor最终的实例化,都是异步的。因为newActor是通过Create消息触发的,而Mailbox对所有消息的处理都是在单独的线程处理的。如果actor的创建过程中有一些线程不安全的代码,就需要注意喽。

Akka源码分析-Actor创建(续)的更多相关文章

  1. Akka源码分析-Actor创建

    上一篇博客我们介绍了ActorSystem的创建过程,下面我们就研究一下actor的创建过程. val system = ActorSystem("firstActorSystem" ...

  2. Akka源码分析-Actor发消息(续)

    上一篇博客我们分析道mailbox同时也是一个forkjointask,run方法中,调用了processMailbox处理一定数量的消息,然后最终调用dispatcher的registerForEx ...

  3. Akka源码分析-Actor&ActorContext&ActorRef&ActorCell

    分析源码的过程中我们发现,Akka出现了Actor.ActorRef.ActorCell.ActorContext等几个相似的概念,它们之间究竟有什么区别和联系呢? /** * Actor base ...

  4. Akka源码分析-Actor发消息

    前面两篇文章简单介绍了ActorSystem.actor以及dispatcher和mailbox的创建,下面我们就来看一下actor发消息的内部机制. val system = ActorSystem ...

  5. Akka源码分析-Remote-Actor创建

    在之前的博客中,我们分析过local模式下Actor的创建过程,最终还是调用了provider的actorOf的函数创建了Actor,在remote模式下provider就是RemoteActorRe ...

  6. Akka源码分析-Akka-Streams-概念入门

    今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...

  7. Akka源码分析-Cluster-Metrics

    一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...

  8. Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster

    在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...

  9. Akka源码分析-Cluster-Singleton

    akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...

随机推荐

  1. eclipse自动换行

    Eclipse是一款非常优秀的IDE,但是不能自动换行,需要安装一个插件完成这个功能. 安装办法有两种: 1.在线安装. 选择help-->install new software,点击Add, ...

  2. 52.基于doc value正排索引的聚合内部原理

    主要知识点: 本节没有太懂,以后复习时补上       聚合分析的内部原理是什么????aggs,term,metric avg max,执行一个聚合操作的时候,内部原理是怎样的呢?用了什么样的数据结 ...

  3. C语言结构体用法

    结构体的定义: 方法一: struct student { char name[10]; int age; int number; }; struct student stu1; 方法二: struc ...

  4. LINUX驱动、系统底层

    就业模拟测试题-LINUX驱动.系统底层工程师职位 本试卷从考试酷examcoo网站导出,文件格式为mht,请用WORD/WPS打开,并另存为doc/docx格式后再使用 试卷编号:143921试卷录 ...

  5. [bzoj2431][HAOI2009][逆序对数列] (dp计数)

    Description 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的 数列,可以很容易求出有多少个逆序对数.那么逆 ...

  6. SQL to MongoDB Mapping Chart

    http://docs.mongodb.org/manual/reference/sql-comparison/ In addition to the charts that follow, you ...

  7. macos-mojave

    macos-mojave https://itunes.apple.com/cn/app/macos-mojave/id1398502828?mt=12

  8. 转载 字符串hash

    转载自:http://www.cnblogs.com/jiu0821/p/4554352.html 求一个字符串的hash值: •现在我们希望找到一个hash函数,使得每一个字符串都能够映射到一个整数 ...

  9. 9、Java并发性和多线程-线程安全与共享资源

    以下内容转自http://ifeve.com/thread-safety/: 允许被多个线程同时执行的代码称作线程安全的代码.线程安全的代码不包含竞态条件.当多个线程同时更新共享资源时会引发竞态条件. ...

  10. css3 transform对其他样式影响,(尤其是position:flixed)

    1.transform 会为当前元素添加 position : relative 特性: 当 magin 为负值的时候,就会覆盖到前面的 元素, 然而如果给前面元素添加了transform 属性后,前 ...