并发编程模型 Akka -并发编程框架
(不需要关注并发情况底层的东西,易开发易维护)
Akka 介绍
写并发程序很难。 程序员不得不处理线程、 锁和竞态条件等等, 这个过程很容易出错, 而且会导致程序代码难以阅读、 测试和维护。
Akka 是 JVM 平台上构建高并发、 分布式和容错应用的工具包和运行时。 Akka 用 Scala 语言写成, 同时提供了 Scala 和 JAVA 的开发接口。
Akka 中 Actor 模型
Akka 处理并发的方法基于 Actor 模型。 在基于 Actor 的系统里, 所有的事物都是 Actor, 就好像在面向对象设计里面所有的事物都是对象一样。 但是有一个重要区别, 那就是 Actor 模型是作为一个并发模型设计和架构的, 而面向对象模式则不是。 Actor 与 Actor 之间只能通
过消息通信。
- 对并发模型进行了更高的抽象
- 异步、 非阻塞、 高性能的事件驱动编程模型(就是不会一个卡主另一个)
- 轻量级事件处理(1GB 内存可容纳百万级别个 Actor)
为什么 Actor 模型是一种处理并发问题的解决方案?
处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢? 无非是我们的程序是多线程的, 多个线程对同一个数据进行修改, 若不加同步条件, 势必会造成数据污染。那么我们是不是可以转换一下思维, 用单线程去处理相应的请求, 但是又有人会问了, 若是用单线程处理, 那系统的性能又如何保证。 Actor 模型的出现解决了这个问题, 简化并发编程, 提升程序性能。
解决这个问题(单线程要排队,多线程引发数据污染)
从图中可以看到, Actor 与 Actor 之前只能用消息进行通信, 当某一个 Actor 给另外一个 Actor
发消息, 消息是有顺序的, 只需要将消息投寄的相应的邮箱, 至于对方 Actor 怎么处理你的
消息你并不知道, 当然你也可等待它的回复。
Actor 是 ActorSystem 创建的, ActorSystem 的职责是负责创建并管理其创建的 Actor,
ActorSystem 的单例的, 一个 JVM 进程中有一个即可, 而 Acotr 是多例的。
akka actor创建过程
actor之间可以进行通信,通信方式是相互传递消息
首先ActorSystem创建Actor,通过ActorRef(可以理解为Actor的代理)发送消息,发送至Dispatcher Message分发器(线程池)进行转发或者分发,发送到自己的MailBox(消息队列)中,消息根据FIFO的规则(先进先出原则),然后调用recive方法接受并且进行处理。
案例1
自己给自己发消息的案例(单机进行)
package cn.sheep.actor
import akka.actor.{Actor, ActorSystem, Props}
class HelloActor extends Actor{
// 接受消息的
override def receive: Receive = {
// 接受消息并处理
case "你好帅" => println("竟说实话,我喜欢你这种人!")
case "丑" => println("滚犊子 !")
case "stop" => {
context.stop(self) // 停止自己的actorRef
context.system.terminate() // 关闭ActorSystem
}
}
}
object HelloActor {
private val nBFactory = ActorSystem("NBFactory")// actorSystem,工厂,参数为工厂的名字
private val helloActorRef = nBFactory.actorOf(Props[HelloActor], "helloActor") // 通过actorSystem创建ActorRef ,参数为actor的名字
def main(args: Array[String]): Unit = {
// 给自己发送消息
helloActorRef ! "你好帅"
helloActorRef ! "丑"
helloActorRef ! "stop"
}
}
案例2:双人相互发消息的案例,模拟龙哥和峰哥打乒乓球
a要给b发消息,要拿到b的ActorRef引用,利用sender直接给a发送回消息
(单机进行)
代码:
龙哥actor
package cn.sheep.actor
import akka.actor.{Actor, ActorRef}
/**
* 马龙,先发消息,所以需要引用到对方actor 的ActorRef(就是峰哥)
*/
class LongGeActor(val fg: ActorRef) extends Actor{
// 接受消息的
override def receive: Receive = {
case "start" => {
println("龙龙:I'm OK !")
fg ! "啪" //马龙中持有的峰哥的ActorRef,即给对方发消息
}
//接收对方发的 消息
case "啪啪" => {
println("你真猛!")
Thread.sleep(1000)
fg ! "啪" //继续发给峰哥消息
}
}
}
峰哥actor
package cn.sheep.actor
import akka.actor.Actor
/**
* 高峰
*/
class FengGeActor extends Actor{
override def receive: Receive = {
case "start" => println("峰峰说:I'm OK !")
//接收到马龙给自己发送的消息
case "啪" => {
println("峰峰:那必须滴!")
Thread.sleep(1000) //停止一秒
sender() ! "啪啪" //给消息发送方返回的消息
}
}
}
开始打
package cn.sheep.actor
import akka.actor.{ActorSystem, Props}
object PingPongApp extends App{
// actorSystem
private val pingPongActorSystem = ActorSystem("PingPongActorSystem")
// 通过actorSystem创建ActorRef
// 创建FengGeActor
private val ffActorRef = pingPongActorSystem.actorOf(Props[FengGeActor], "ff")
// 创建LongGeActorRef,new LongGeActor(ffActorRef))的意思是需要将接收方的ActorRef作为参数传入
private val mmActorRef = pingPongActorSystem.actorOf(Props(new LongGeActor(ffActorRef)), "mm")
//峰哥给自己发送消息
ffActorRef ! "start"
//马龙给自己发送消息
mmActorRef ! "start"
}
案例三
需要知道serverActor的ip和端口
serverActor
class Edu360Server extends Actor{
// 用来接受客户端发送过来的问题的
override def receive: Receive = {
case "start" => println("老娘已就绪 !")
case ClientMessage(msg) => {
println(s"收到客户端消息:$msg")
msg match {
case "你叫啥" => sender() ! ServerMessage("铁扇公主")
case "你是男是女" => sender() ! ServerMessage("老娘是男的")
case "你有男票吗" => sender() ! ServerMessage("没有")
case _ => sender() ! ServerMessage("What you say ?") //sender()发送端的代理对象, 发送到客户端的mailbox中 -> 客户端的receive
}
}
}
}
object Edu360Server {
def main(args: Array[String]): Unit = {
val host = "127.0.0.1"
val port = 8088
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
// 指定IP 和 端口
val actorSystem = ActorSystem("Server", config)
orRef = actorSystem.actorOf(Props[Edu360Server], "shanshan")
serverActorRef ! "start" // 到自己的mailbox -》 receive方法
}
}
ClientActor
package cn.sheep.robot
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
class ClientActor(host: String, port: Int) extends Actor{
var serverActorRef: ActorSelection = _ // 服务端的代理对象
// 在receive方法之前调用
override def preStart(): Unit = {
// akka.tcp://Server@127.0.0.1:8088
serverActorRef = context.actorSelection(s"akka.tcp://Server@${host}:${port}/user/shanshan")
}
// mailbox ->receive
override def receive: Receive = { // shit
case "start" => println("牛魔王系列已启动...")
case msg: String => { // shit
serverActorRef ! ClientMessage(msg) // 把客户端输入的内容发送给 服务端(actorRef)--》服务端的mailbox中 -> 服务端的receive
}
case ServerMessage(msg) => println(s"收到服务端消息:$msg")
}
}
object ClientActor {
def main(args: Array[String]): Unit = {
val host = "127.0.0.1"
val port = 8089
val serverHost = "127.0.0.1"
val serverPort = 8088
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
val clientSystem = ActorSystem("client", config)
// 创建dispatch | mailbox
v al actorRef = clientSystem.actorOf(Props(new ClientActor(serverHost, serverPort.toInt)), "NMW-002")
actorRef ! "start" // 自己给自己发送了一条消息 到自己的mailbox => receive
while (true) {
val question = StdIn.readLine() // 同步阻塞的, shit
actorRef ! question // mailbox -> receive
}
}
}
样例类
package cn.sheep.robot
// 样例类,使用 case 关键字 修饰的类, 其重要的特征就是支持模式匹配
// 样例类默认是实现了序列化接口
// 服务端发送给客户端的消息格式
case class ServerMessage(msg: String)
// 客户端发送给服务器端的消息格式
case class ClientMessage(msg: String)
来源:CSDN
作者:heartless_killer
链接:https://blog.csdn.net/heartless_killer/article/details/104333991