在上一遍博客中,我们已经分析了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. [Luogu] P4626 一道水题 II

    ---恢复内容开始--- 题目描述 一天,szb 在上学的路上遇到了灰太狼. 灰太狼:帮我们做出这道题就放了你. szb:什么题? 灰太狼:求一个能被 [1,n] 内所有数整除的最小数字,并对 100 ...

  2. Linux修改系统时间与时区

    GMT  (Greewich Mean Time) 格林威治标准时间:GMT是老的时间计量标准,根据地球的自转和公转来计算时间,也就是太阳每天经过位于英国伦敦郊区的皇家格林尼治天文台的标准时间就是中午 ...

  3. Shell脚本中非交互式修改密码的方法(转)

    这篇文章主要介绍了Shell脚本中非交互式修改密码的两种方法,本文讲解了使用chpasswd和使用passwd和--stdin组合两种方法,需要的朋友可以参考下. 对系统定期修改密码是一个很重要的安全 ...

  4. pageContext对象的使用及常用方法

    pageContext对象的使用及常用方法 制作人:全心全意 获取页面上下文的pageContext对象是一个比较特殊的对象,通过它可以获取JSP页面的request.response.session ...

  5. 【DIP, OpenCV】Some Kinds Of Image Smoothing Methodologies

    In digital image processing(DIP), many methods are used in smoothing images in order to suppress noi ...

  6. maven使用nexus3.3在windows下搭建私服

    1. 私服简介 私服是指私有服务器,是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构建.有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库:否则 ...

  7. 170611 NOIP模拟赛

    第一题没做出来不应该: 第二题不难想,就是写起来很麻烦: 第三题因为学了挺久的splay就直接写的splay,没太在意常数问题,一般情况下,第k值问题主席树是比splay稍快的: 盘子序列 [题目描述 ...

  8. HDU——1133 Buy the Ticket

    Buy the Ticket Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) T ...

  9. LVS中文站点

    http://blog.csdn.net/turkeyzhou/article/details/16980161 http://zh.linuxvirtualserver.org/

  10. Oracle GV$VIEW

    The catclustdb.sql script creates the GV$ views. Run this script if you do not create your database ...