1.定义Actor
一个Actor 也是一个对象,但是你从来都不会直接调用它的方法,而是通过发送消息,并且每个Actor 都由一个消息队列支撑。如果一个Actor 正忙于处理消息,那么到达的消息将会被插入消息队列中,而不会阻塞消息的发送者;它们发送并忘记(fire-and-forget)。在任意给定的时间,一个Actor 将只会处理一条消息。Actor 模型具有与生俱来的线程安全性。
让我们定义一个 Actor。
class HollywoodActor() extends Actor { def receive: Receive = { case message => println(s"playing the role of $message") } }
Scala 使用来自Akka 的Actor 模型支持—一个使用Scala 编写的非常强大的反应式库。要创建一个Actor,需要继承Actor 特质并实现receive()方法。receive()方法的主体部分看起来非常熟悉,它是去掉了match 关键字的模式匹配语法。该匹配发生在一个隐式的消息对象上。该方法的主体是一个偏函数。
2.使用Actor
在上面例子中,我们只简单地打印了接收到的消息,我们将很快为该 Actor 添加更多的逻辑。让我们使用刚刚定义的Actor。
object CreateActors extends App { val system = ActorSystem("sample") val depp = system.actorOf(Props[HollywoodActor]) depp ! "Wonka" val terminateFuture = system.terminate() Await.ready(terminateFuture, Duration.Inf) }
Akka 的Actor 托管在一个ActorSystem 中,它管理了线程、消息队列以及Actor 的生命周期。相对于使用传统的new 关键字来创建实例,我们使用了一种特殊的actorOf 工厂方法来创建Actor,并将其对应的ActorRef 赋值给了名为depp 的引用。此外,我们也没有使用传统的方法调用语法,而是发送了一个“Wonka”消息给Actor—在这个例子中只传递了一个字符串—我们使用了名为!的方法,你可以使用一个名为tell()的方法,而不是使用!()方法,但是那样就需要传递一个额外的sender 参数。同时,如果你使用的方法名对阅读者来说是直观的,那么你的代码也就太简单了。说到直觉,它们真应该被称为action()。
Actor System 管理了一个线程池,只要系统保持活跃,这个线程池就会一直保持活跃。如果要使该程序在main 代码块执行完成之后关闭,就必须要调用该ActorSystem 的terminate()方法,也就是说,退出它的线程。
编译代码:
scalac -d classes HollywoodActor.scala CreateActors.scala
因为 Scala 的安装中已经包含了Akka 的Actor 库,所以要编译这段代码,我们不需要在classpath 中包含任何其他内容,同样,要运行它,我们也不需要包含任何附加的库。
运行代码:
scala -classpath classes CreateActors
输出结果:
playing the role of Wonka
3.Actor多线程案例
修改一下上面 receive()方法:
case message => println(s"$message - ${Thread.currentThread}")
当接收到消息时,我们把执行线程的详细信息一起打印出来。让我们更改一下对应的调用代码,以便向多个Actor 发送多条消息:
val depp = system.actorOf(Props[HollywoodActor]) val hanks = system.actorOf(Props[HollywoodActor]) depp ! "Wonka" hanks ! "Gump" depp ! "Sparrow" hanks ! "Phillips" println(s"Calling from ${Thread.currentThread}")
这将为我们提供一些更加有趣的细节。让我们运行这段代码并查看输出结果:
Wonka - Thread[sample-akka.actor.default-dispatcher-2,5,main] Gump - Thread[sample-akka.actor.default-dispatcher-3,5,main] Calling from Thread[main,5,main] Phillips - Thread[sample-akka.actor.default-dispatcher-3,5,main] Sparrow - Thread[sample-akka.actor.default-dispatcher-2,5,main]
我们给每个 Actor 都发送了两条消息:给Actor depp 发送了“Wanka”和“Sparrow”,给Actor hanks 发送了“Gump”和“Phillips”。看到这个输出结果估计很多人就想起了Java的线程池Executors,
但这里我们并没有显式地创建一个线程池,也没有显式地调度任务。我们只是向Actor 发送了一条消息,而ActorSystem 则负责了所有剩下的事情。
4.Actor特点:
一个可用的线程池,不必大惊小怪。
Actor 在不同的线程中运行,而不是调用代码的主线程。
每个Actor 一次只处理一条消息。
多个Actor 并发地运行,同时处理多条消息。
Actor 是异步的。
不会阻塞调用者—main 方法(直接)运行了println()方法,根本不会等待这些Actor 的回复。