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. Maven项目中添加JDBC驱动

    在pom.xml配置文件中添加: <dependency> <groupId>mysql</groupId> <artifactId>mysql-con ...

  2. pandas修改全列的时间格式 无需使用apply

    df.date.dt.strftime('%Y%m%d') #实现全列修改时间格式

  3. eclipse插件wordwrap

    一行代码很长,浏览不方便,安装wordwrap可以自动折行. help->install new software-,在Workwith输入wordwrap - http://ahtik.com ...

  4. Java使用HTTPClient4.3开发的公众平台消息模板的推送功能

    代码引用,参考文章:http://www.cnblogs.com/feiyun126/p/4778556.html,表示感谢! package com.yuanchuangyun.cyb.manage ...

  5. 数据驱动测试之——CSV+TestNG

    对于利用Webdriver做自动化的童鞋,对于如何将元素或者输入数据如何和编码分离都应该不会太陌生,本着一边学习一边分享的心态,大概总结了一下关于利用CSV.XML以及Excel来存放数据,然后在结合 ...

  6. C++与C的区别

    在最开始C++只是C加上了一些面向对象的特性,C++最初的名字为C with Classes.后来C++又提出了一些不同于Class的特性:Exceptions(异常).templates(模板).S ...

  7. DTD举例一

    DTD举例一: <!--动作库约束文件--> <!DOCTYPE actionGroup [ <!ELEMENT actionGroup (action*)> <! ...

  8. Linux 4.21包含对AMD Rome处理器中新的Zen 2架构重要的新优化

    导读 Phoronix的Linux爱好者报告说,Linux 4.21里包含对AMD Rome处理器中新的Zen 2架构重要的新优化.AMD新推出的7nm EPYC Rome芯片带来了一种全新的独特架构 ...

  9. python中 Lambda,Map,Filter,Itertools,Generator高级函数的用法

    Lambda 函数 Lambda 函数是一种比较小的匿名函数--匿名是指它实际上没有函数名. Python 函数通常使用 def a_function_name() 样式来定义,但对于 lambda ...

  10. BZOJ4551[Tjoi2016&Heoi2016]树——dfs序+线段树/树链剖分+线段树

    题目描述 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为1),有以下 两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均 ...