I read Akka documentation, but I do not understand:
class MyActor extends Actor {
private var _state = 0
override def receive: Receive = {
case x: Int =>
if (x != _state) {
println(s"---------> fail: ${x} and ${_state}")
}
_state = x + 1
}
}
implicit val system = ActorSystem("my-system")
val ref = system.actorOf(Props[MyActor], "my-actor")
(0 to 10000).foreach { x =>
ref ! x
}
I have a _state variable which is not @volatile and not atomic but at the same time _state is always correct, if I do changes with !-method.
How does Akka protect and update the internal state of actors?
Akka is an implementation of the Actor Model of computation. One of the (arguably the) key guarantees made by the actor model is that an actor only ever processes a single message at a time. Simply by virtue of having
_statebe private to an actor, you get a concurrency guarantee that's at least as strong as if you had every method of an object be@synchronized, with the added bonus of the!operation to send a message being non-blocking.Under the hood, a rough (simplified in a few places, but the broad strokes are accurate) outline of how it works and how the guarantee is enforced is:
Using the
PropstheActorSystemconstructs an instance ofMyActor, places the only JVM reference to that instance inside anActorCell(I'm told this terminology, as well as that the deep internals of Akka are "the dungeon", is inspired by the early development team for Akka being based in an office in what was previously a jail in Uppsala, Sweden), and keys thatActorCellwithmy-actor. In the meantime (technically this happens aftersystem.actorOfhas already returned theActorRef), it constructs anActorRefto allow user code to refer to the actor.Inside the
ActorCell, thereceivemethod is called and the resultingPartialFunction[Any, Unit](which has a type synonym ofReceive) is saved in a field of theActorCellwhich corresponds to the actor's behavior.The
!operation on theActorRef(at least for a localActorRef) resolves which dispatcher is responsible for the actor and hands the message to the dispatcher. The dispatcher then enqueues the message into the mailbox of theActorCellcorresponding tomy-actor(this is done in a thread-safe way).If there is no task currently scheduled to process messages from the actor's mailbox, such a task is enqueued to the dispatcher's execution context to dequeue some (configurable) number of messages from the
ActorCell's mailbox and process them, one-at-a-time, in a loop. After that loop, if there are more messages to process, another such task will be enqueued. Processing a message consists of passing it to theReceivestored in theActorCell's behavior field (this mechanism allows thecontext.becomepattern for changing behavior).It's the last bit that provides the core of the guarantee that only one thread is ever invoking the
Receivelogic.