Scala 创建使用Actor 多线程

2019-05-23 17:15:42 | 编辑 | 添加

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 的回复。