Scala进阶之路-并发编程模型Akka入门篇

                                  作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.Akka Actor介绍

1>.Akka介绍

  写并发程序很难。程序员不得不处理线程、锁和竞态条件等等,这个过程很容易出错,而且会导致程序代码难以阅读、测试和维护。Akka 是 JVM 平台上构建高并发、分布式和容错应用的工具包和运行时。Akka 用 Scala 语言写成,同时提供了 Scala 和 JAVA 的开发接口。

2>.Akka 中 中 Actor  模型

  Akka 处理并发的方法基于 Actor 模型。在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。但是有一个重要区别,那就是 Actor 模型是作为一个并发模型设计和架构的,而面向对象模式则不是。Actor 与 Actor 之间只能通过消息通信。

3>.Akaka的特点

  第一:它是对并发模型进行了更高的抽象;
  第二:它是异步、非阻塞、高性能的事件驱动编程模型;
  第三:它是轻量级事件处理(1GB 内存可容纳百万级别个 Actor);

4>.为什么 Actor 模型是一种处理并发问题的解决方案?

  处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢?无非是我们的程序是多线程的,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。那么我们是不是可以转换一下思维,用单线程去处理相应的请求,但是又有人会问了,若是用单线程处理,那系统的性能又如何保证。Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。

5>.Maven依赖

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>cn.org.yinzhengjie</groupId>
<artifactId>MyActor</artifactId>
<version>1.0-SNAPSHOT</version> <!-- 定义一下常量 -->
<properties>
<encoding>UTF-8</encoding>
<scala.version>2.11.8</scala.version>
<scala.compat.version>2.11</scala.compat.version>
<akka.version>2.4.17</akka.version>
</properties> <dependencies>
<!-- 添加scala的依赖 -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency> <!-- 添加akka的actor依赖 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_${scala.compat.version}</artifactId>
<version>${akka.version}</version>
</dependency> <!-- 多进程之间的Actor通信 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_${scala.compat.version}</artifactId>
<version>${akka.version}</version>
</dependency>
</dependencies> <!-- 指定插件-->
<build>
<!-- 指定源码包和测试包的位置 -->
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<!-- 指定编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin> </plugins>
</build>
</project>

  自定义默认的源代码包和测试包的位置,需要手动穿件Source Root目录哟,如下图:

二.编写HelloActor

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.actor import akka.actor.{Actor, ActorSystem, Props} import scala.io.StdIn class HelloActor extends Actor{
// 重写接受消息的偏函数,其功能是接受消息并处理
override def receive: Receive = {
case "你好帅" => println("竟说实话,我喜欢你这种人!")
case "丑八怪" => println("滚犊子 !")
case "stop" => {
context.stop(self) // 停止自己的actorRef
context.system.terminate() // 关闭ActorSystem,即关闭其内部的线程池(ExcutorService)
}
}
} object HelloActor {
/**
* 创建线程池对象MyFactory,用来创建actor的对象的
*/
private val MyFactory = ActorSystem("myFactory") //里面的"myFactory"参数为线程池的名称
/**
* 通过MyFactory.actorOf方法来创建一个actor,注意,Props方法的第一个参数需要传递我们自定义的HelloActor类,
* 第二个参数是给actor起个名字
*/
private val helloActorRef = MyFactory.actorOf(Props[HelloActor], "helloActor") def main(args: Array[String]): Unit = {
var flag = true
while(flag){
/**
* 接受用户输入的字符串
*/
print("请输入您想发送的消息:")
val consoleLine:String = StdIn.readLine()
/**
* 使用helloActorRef来给自己发送消息,helloActorRef有一个叫做感叹号("!")的方法来发送消息
*/
helloActorRef ! consoleLine
if (consoleLine.equals("stop")){
flag = false
println("程序即将结束!")
}
/**
* 为了不让while的运行速度在receive方法之上,我们可以让他休眠0.1秒
*/
Thread.sleep(100)
}
}
}

  以上代码执行结果如下:

三.两个actor通信案例-模拟下棋对话

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.actor import akka.actor.{ActorSystem, Props} import akka.actor.{Actor, ActorRef} /**
* 定义玩家1
*/
class player1Actor(val p2: ActorRef) extends Actor{
// receive方法是负责处理消息的
override def receive: Receive = {
case "start" => {
println("棋圣:I'm OK !")
p2 ! "该你了"
}
case "将军" => {
println("棋圣:你真猛!")
Thread.sleep(1000)
p2 ! "该你了"
}
}
} /**
* 定义玩家2
*/
class player2Actor extends Actor{ override def receive: Receive = {
case "start" => println("棋仙说:I'm OK !")
case "该你了" => {
println("棋仙:那必须滴!")
Thread.sleep(1000)
/**
* 注意,这个“sender()”,其实就是对ActorRef的一个引用。它指的是给发送"该你了"的这个对象本身!
*/
sender() ! "将军"
}
}
} object ChineseChess extends App{
// 创建 actorSystem的工厂,用来生产ActorRef对象!
private val ChineseChessActorSystem = ActorSystem("Chinese-chess")
/**
* 通过actorSystem创建ActorRef
*/
private val p2 = ChineseChessActorSystem.actorOf(Props[player2Actor], "player2") //创建player2Actor对象
private val p1 = ChineseChessActorSystem.actorOf(Props(new player1Actor(p2)), "player1") //创建player1Actor对象 p2 ! "start"
p1 ! "start"
}

  运行以上代码输出结果如下:

四.服务端和客户端交互的小程序

1>.服务端代码

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.robot import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory class ServerActor extends Actor{
/**
* receive方法是用来处理客户端发送过来的问题的
*/
override def receive: Receive = {
case "start" => println("天猫系统已启动...") case ClientMessage(msg) => {
println(s"收到客户端消息:$msg")
msg match {
/**
* sender()发送端的代理对象, 发送到客户端的mailbox中 -> 客户端的receive
*/
case "你叫啥" =>
sender() ! ServerMessage("本宝宝是天猫精灵")
case "你是男是女" =>
sender() ! ServerMessage("本宝宝非男非女")
case "你有男票吗" =>
sender() ! ServerMessage("本宝宝还小哟")
case "stop" =>
context.stop(self) // 停止自己的actorRef
context.system.terminate() // 关闭ActorSystem,即关闭其内部的线程池(ExcutorService)
println("天猫系统已停止...")
case _ =>
sender() ! ServerMessage("对不起,主人,我不知道你在说什么.......")
}
}
}
} object ServerActor {
def main(args: Array[String]): Unit = {
//定义服务端的ip和端口
val host = "127.0.0.1"
val port = 8088
/**
* 使用ConfigFactory的parseString方法解析字符串,指定服务端IP和端口
*/
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
/**
* 将config对象传递给ActorSystem并起名为"Server",为了是创建服务端工厂对象(ServerActorSystem)。
*/
val ServerActorSystem = ActorSystem("Server", config)
/**
* 通过工厂对象创建服务端的ActorRef
*/
val serverActorRef = ServerActorSystem.actorOf(Props[ServerActor], "Miao~miao")
/**
* 到自己的mailbox -》 receive方法
*/
serverActorRef ! "start"
}
}

2>.客户端代码

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Scala%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.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/Miao~miao")
}
// mailbox ->receive
override def receive: Receive = { // shit
case "start" => println("2018天猫精灵为您服务!")
case msg: String => { // shit
serverActorRef ! ClientMessage(msg) // 把客户端输入的内容发送给 服务端(actorRef)--》服务端的mailbox中 -> 服务端的receive
}
case ServerMessage(msg) => println(s"收到服务端消息:$msg")
}
} object ClientActor {
def main(args: Array[String]): Unit = { //指定客户端的IP和端口
val host = "127.0.0.1"
val port = 8089 //指定服务端的IP和端口
val serverHost = "127.0.0.1"
val serverPort = 8088 /**
* 使用ConfigFactory的parseString方法解析字符串,指定客户端IP和端口
*/
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin) /**
* 将config对象传递给ActorSystem并起名为"Server",为了是创建客户端工厂对象(clientActorSystem)。
*/
val clientActorSystem = ActorSystem("client", config) // 创建dispatch | mailbox
val clientActorRef = clientActorSystem.actorOf(Props(new ClientActor(serverHost, serverPort.toInt)), "Client") clientActorRef ! "start" // 自己给自己发送了一条消息 到自己的mailbox => receive /**
* 接受用户的输入信息并传送给服务端
*/
while (true) {
Thread.sleep(500)
/**
* StdIn.readLine方法是同步阻塞的
*/
val question = StdIn.readLine("请问有什么我可以帮你的吗?>>>")
clientActorRef ! question
if (question.equals("stop")){
Thread.sleep(500)
println("程序即将结束")
System.exit(0)
}
}
}
}

3>.先执行服务端再执行客户端并输入相应信息测试结果如下:

  客户端运行结果如下:

  服务端运行结果如下:

Scala进阶之路-并发编程模型Akka入门篇的更多相关文章

  1. Scala并发编程模型AKKA

    一.并发编程模型AKKA Spark使用底层通信框架AKKA 分布式 master worker hadoop使用的是rpc 1)akka简介 写并发程序很难,AKKA解决spark这个问题. akk ...

  2. Scala进阶之路-面向对象编程之类的成员详解

    Scala进阶之路-面向对象编程之类的成员详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Scala中的object对象及apply方法 1>.scala 单例对象 ...

  3. 15. Scala并发编程模型Akka

    15.1 Akka介绍 1) Akka是Java虚拟机JVM平台上构建高并发.分布式和容错应用的工具包和运行时,可以理解成Akka是编写并发程序的框架 2) Akka用Scala语言写成,同时提供了S ...

  4. Scala-Unit7-Scala并发编程模型AKKA

    一.Akka简介 Akka时spark的底层通信框架,Hadoop的底层通信框架时rpc. 并发的程序编写很难,但是Akka解决了spark的这个问题. Akka构建在JVM平台上,是一种高并发.分布 ...

  5. Scala进阶之路-idea下进行spark编程

    Scala进阶之路-idea下进行spark编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 1>.创建新模块并添加maven依赖 <?xml version=&qu ...

  6. 4、Java并发性和多线程-并发编程模型

    以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...

  7. Scala进阶之路-Spark本地模式搭建

    Scala进阶之路-Spark本地模式搭建 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Spark简介 1>.Spark的产生背景 传统式的Hadoop缺点主要有以下两 ...

  8. Scala进阶之路-Spark底层通信小案例

    Scala进阶之路-Spark底层通信小案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Spark Master和worker通信过程简介 1>.Worker会向ma ...

  9. Scala进阶之路-尾递归优化

    Scala进阶之路-尾递归优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 递归调用有时候能被转换成循环,这样能节约栈空间.在函数式编程中,这是很重要的,我们通常会使用递归方法来 ...

随机推荐

  1. github心得体会 王倩倩 201303014004 计科高职13-1

    刚开始接触一门语言软件特别无从下手,尤其还是全英文的,真的是很头疼,注册的时候这个就弄了半天,在网上搜了一下教程然后又结合着老师上课讲的内容自己多做了几遍,也算是对github熟悉了,然后学会操作代码 ...

  2. SE Springer小组之《Spring音乐播放器》可行性研究报告五、六

    5 可选择的其他系统方案 曾经考虑过制作闹钟系统,但考虑到闹钟系统在电脑应用中极其不实用,所以此方案未通过. 6 投资及效益分析 6.1支出 本软件只用于完成课程学习要求,不用做商用,无基础设备等支出 ...

  3. 微信小程序动态数据跑马灯组件编写

    开发必备:熟悉微信小程序组件开发 开发思路:如果只有一条数据,直接用css3关键帧动画:如果有多条数据,则在当前动画运动到一定时间的时候,将其数据替换掉,使之在视觉效果上有一个从下到上播放的状态.数组 ...

  4. Install alipay支付宝安全控件 on firefox in linux

    [root@rgqancy 下载]# ./aliedit.sh建议以非root账号安装支付宝安全控件请重启   firefox   使插件生效成功安装 支付宝安全控件请按任意键退出... what i ...

  5. Windows Server 2008 双网卡 断网问题 总结

    实施现场的情况,一个网卡接得是聚合APN的子网,一个网卡是借得局域网. 运行一份数据收发程序,从APN网接入数据,发送给局域网,程序启动一会儿后就崩溃,此时测试网卡就Ping网关了,或者是时断时续,逐 ...

  6. Oracle 数据表误删恢复 Flashback

    1. 前提条件. recyclebin 参数打开. 验证参数是否打开: SHOW PARAMETER RECYCLEBIN 2. 如果参数没有打开的话 需要打开,并且重启一下数据库方法为 alter ...

  7. spring学习总结(一)_Ioc基础(上)

    最近经历了许许多多的事情,学习荒废了很久.自己的目标成了摆设.现在要奋起直追了.最近发现了张果的博客.应该是一个教师.看了他写的spring系列的博客,写的不错.于是本文的内容参考自他的博客,当然都是 ...

  8. C# PictureBox控件畫圖

    PictureBox的正方向: BitMap初始化: Bitmap bt = new Bitmap(Width,Height);      Graphics gdi = Graphics.FromIm ...

  9. jsp servlet 的 请求转发和重定向

    以前对于servlet和servlet/jsp之间的跳转路径问题感到很迷惑,今天亲自动手实验了一把,总结如下: servlet已经是项目根路径下面的资源了,所以servlet跳转的时候,在跳转路径上面 ...

  10. ajax 提交字符串到后台 反序列化

    MVC后台 或者 Webapi 都可以使用此方式 前台 @using (Html.BeginForm("Test","Test")) { <input t ...