Actor
- 什么是Scala Actor
(1)Scala中的Actor能够实现并行编程的强大功能,他是基于事件模板的并发机制。Scala是运用消息的发送接、收来实现多线程的。使用Scala能够更容易地实现多线程应用的开发。
多线程不是提高程序运行效率而是提高资源利用率
(2)并发(concurrency)和并行(parallellism)是:
解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。也就是说,并发可以是虚拟的同时执行,也可以是真的同时执行。
- 传统的java并发编程与Scala Actor编程的区别
对于Java,我们都知道它的多线程实现需要对共享资源(变量、对象等)使用synchronized 关键字进行代码块同步、对象锁互斥等等。而且,常常一大块的try…catch语句块中加上wait方法、notify方法、notifyAll方法是让人很头疼的。原因就在于Java中多数使用的是可变状态的对象资源,对这些资源进行共享来实现多线程编程的话,控制好资源竞争与防止对象状态被意外修改是非常重要的,而对象状态的不变性也是较难以保证的。 而在Scala中,我们可以通过复制不可变状态的资源(即对象,Scala中一切都是对象,连函数、方法也是)的一个副本,再基于Actor的消息发送、接收机制进行并行编程
Scala中的并发编程:
①Scala中的并发编程思想与Java中的并发编程思想完全不一样,Scala中的Actor是一种不共享数据,依赖于消息传递的一种并发编程模式, 避免了死锁、资源争夺等情况。在具体实现的过程中,Scala中的Actor会不断的循环自己的邮箱,并通过receive偏函数进行消息的模式匹配并进行相应的处理。
②如果Actor A和 Actor B要相互沟通的话,首先A要给B传递一个消息,B会有一个收件箱,然后B会不断的循环自己的收件箱, 若看见A发过来的消息,B就会解析A的消息并执行,处理完之后就有可能将处理的结果通过邮件的方式发送给A。
- Actor方法执行顺序
(1)首先调用start()方法启动Actor
(2)调用start()方法后其act()方法会被执行
Scala Actor向Actor发送消息
- 发送消息的方式
! 发送异步消息,没有返回值。
!? 发送同步消息,等待返回值。
!! 发送异步消息,返回值是 Future[Any]。
- 同步交互与异步交互
Java中交互方式分为同步消息处理和异步消息处理两种:
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
我们的项目开发中都会优先选择不需要等待的异步交互方式。同步交互比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互。
- Actor的案例应用
(1)创建Actor
//继承Actor特质类 class HiActor extends Actor { //重写Actor特质类的抽象方法act,从而实现自己的业务逻辑 override def act(): Unit = { //消息循环 while (true) //使用case模式匹配接收到的消息 receive { case "Hi" => println("Hello") } } } |
- 启动Actor
val actor1 = new HiActor actor1.start()
|
- Actor执行是异步的(不需要等待返回结果,可以直接进行其他的线程)
package actor
结果: this is Main ..... this is Actor..... this is Main ..... this is Actor..... this is Main .....
|
- 同步执行,没有返回结果(不会执行下一个请求)
package actor
结果: this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor..... this is Actor.....
|
- 同步执行有返回结果(会执行下一个请求)
package actor 结果: this is Actor..... this is Actor..... start Main..... this is Main ..... this is Actor..... this is Main ..... this is Actor..... this is Main ..... this is Actor..... this is Main ..... this is Actor..... |
- 发送消息
actor1 ! "Hi"
- 用样例类作为消息,actor就可以使用样式匹配来处理消息了
package actor
结果: the name is 张三,the age is 18 the name is 李四,the age is 19 the name is 赵六,the age is 16
|
- 发送到actor的消息被存放在一个“邮箱”中。Receive方法从邮箱获取下一条消息并将它传递给它的参数,该参数是一个偏函数。
receive{ case Deposit(amount) => ... case Withdraw(amount) => ... } |
- 向其他的Actor发送消息
当运算被分拆到不同actor来并行处理问题的各个部分时,这些处理结果需要被收集到一起。Actor可以将结果存入到一个线程安全的数据结构当中,比如一个并发的哈希映射,但actor模型并不鼓励使用共享数据。因而当actor计算出结果后,应该向另一个actor发送消息。
Actor计算结果发送方向:
可以是一些全局的actor。不过,当actor数量很多时,该方案伸缩性并不好。
Actor可以构造成带有指向另一个或多个actor的引用。
Actor可以接收带有指向另一个actor的引用消息。
Actor可以返回消息给发送方。
- 共享线程
某些程序包含的actor过多,以至于要为actor创建单独的线程开销会很大。此时需要考虑在同一个线程中运行多个actor。Actor有时大部分时间用户等待消息,这时actor所在的单独线程会堵塞,与其这样不如用一个线程来执行多个actor的消息处理函数。
在Scala中,react方法可以实现这样的功能。react方法接收一个偏函数,并将它添加到邮箱,然后退出。
react工作原理
当你调用一个actor的start时,start方法会以某种方式来确保最终会有某个线程来调用那个actor的act方法。如果act方法调用了react ,则react方法会在actor的邮箱中查找传递给偏函数的能够处理的消息 。(和receive方法一样,传递待处理消息给偏函数的isDefinedAt方法。) 如果找到一个可以处理的消息,react 会安排一个在未来某个时间处理该消息的计划并抛出异常。如果它没有找到这样的消息,它会将actor置于“冷存储” 状态 ,在它通过邮箱收到更多消息时重新激活,并抛出异常。不论是哪种情况,react都会以这个异常的方式完成其执行,act 方法也随之结束 调用act的线程会捕获这个异常,忘掉这个actor , 并继续处理其他事务。这就是为什么你想要react在处理第一个消息之外做更多的事,你将需要在偏函数中再次调用act方法 ,或使用某种其他的手段来让react再次被调用。
不同actor可以通过react而不是receive来共享线程,前提是消息处理器的控制流转足够简单。
import scala.actors.Actor
|
- 多线程wordCount
package actor
|
- Actor的生命周期
actor的act方法在actor的start方法被调用的时候开始执行。通常,actor接下来做的事情是进入某个循环,例如:
def act(): Unit ={ } |
actor在如下情形之一会终止执行:
Act方法返回
Act方法由于异常被终止
Actor调用exit方法