Actor概述
接收消息
Actor中使用receive方法来接收消息,需要给receive方法传入一个偏函数 注意:receive方法只接收一次消息,接收完后继续执行act方法
{
case 变量名1 : 消息类型1 => 业务处理1 ,
case 变量名2 : 消息类型2 => 业务处理2 ,
. . .
}
总结
1.继承Actor,重写act()
2.发送的sender的act方法中:
3.接收的receiver的act方法中 例:
receive{
case msg: String => println( "接收到的消息为:" + msg)
}
4.class定义的Actor,需要new之后start
5.发送消息可以使用 !(异步无返回)、!?(同步等待返回)、!!(异步有返回)。 6.接受消息可以使用关键字receive。 7.还可以使用模式匹配的方式,对接受到的不同消息的指令作出对应的操作。 8.复用线程
loop{
react{
case msg: String => println( "接收到的消息为:" + msg)
}
}
9.复杂类型中
9.1case的是样例类 9.2返回消息用sender
例: sender ! ReplyMessage(“不好”,“jack”)//ReplyMessage为样例类 9.3异步有返回–异步发送无需等待且后续可以根据future获取异步返回的结果
巧用 while(!future.isSet){}//没有返回结果就空循环 apply()能取出来返回的消息,asInstanceOf[]能把消息转换成需要的样例类
Actor介绍/说明
Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。 Actor可以看作是一个个独立的实体,可以理解为Java中的Thread 但是Java中的Thread之间的通信使用的是等待唤醒机制,而Actor之间的通信使用的是消息 消息的类型可以是任意的,消息的内容也可以是任意的。 一个Actor收到其他Actor的信息后,它可以根据需要作出各种响应。 综上,Actor是一种基于事件模型的并发机制,是运用消息的发送、接收来实现高并发的。 Scala中的ActorAPI已经过时了/过期了 我们现在学习它只是为了完成一个多线程版的WordCount案例和为后续学习Akka做铺垫!
Actor VS Java多线程
Java中并发:
在Java并发编程中,每个对象都有一个逻辑监视器(monitor),可以用来控制对象的多线程访问。 我们添加sychronized关键字来标记,需要进行同步加锁访问。 这样,通过加锁的机制来确保同一时间只有一个线程访问共享数据。 但这种方式存在资源争夺、以及死锁问题,程序越大问题越麻烦。 共享数据 线程死锁
Actor并发编程模型
处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢? 无非是我们的程序是多线程的,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。 Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。 Actor是一种与Java并发编程完全不一样的并发编程模型 Actor使用一种基于事件模型的并发机制,是一种不共享数据,依赖消息传递的一种并发编程模式,有效避免资源争夺、死锁等情况。 Actor 与Actor 之前只能用消息进行通信,当某一个Actor 给另外一个Actor发消息,只需要将消息投寄的相应的邮箱,至于对方Actor 怎么处理你的消息你并不知道,当然你也可等待它的回复。 Actor是Scala初期为了解决Java多线程存在的一些问题而提出来的一个多线程编程API
Java内置线程模型 scala Actor模型 "共享数据-锁"模型 (share data and lock) share nothing不共享 每个object有一个monitor,监视线程对共享数据的访问 不共享数据,Actor之间通过Message通讯 也就是通过传递消息进行通信 加锁代码使用synchronized标识 没有共享数据不需要加锁 死锁问题 没有共享数据不存在死锁 每个线程内部是顺序执行的 每个Actor内部是顺序执行的
总结:
Scala中的Actor其实就是Java中的Thread的加强/改进 Actor用基于事件的消息传递避免了数据共享带来的一些问题 ,如加锁效率低,死锁over等…
Actor发送消息的方式
! 发送异步消息,没有返回值。如Actor1将消息异步发送给Actor2,就认为发生成功,没有返回值 !? 发送同步消息,等待返回值。如Actor1将消息同步发送给Actor2,需要等待Actor2返回结果 !! 发送异步消息,返回值是 Future[Any]。如Actor1将消息异步发送给Actor2,就认为发生成功,返回值会被封装在Future
准备环境
方式一:
在原来的项目基础之上添加SDK 如果不行得使用方式二
方式二:
方式三:
入门案例-1-创建Actor
package cn. hanjiaxiaozhi. actor
import scala. actors. Actor
object ActorDemo1 {
class Actor1 extends Actor{
override def act( ) : Unit = {
( 1 to 10 ) . foreach( println)
}
}
class Actor2 extends Actor{
override def act( ) : Unit = {
( 11 to 20 ) . foreach( println)
}
}
object Actor3 extends Actor{
override def act( ) : Unit = {
( 21 to 30 ) . foreach( println)
}
}
object Actor4 extends Actor{
override def act( ) : Unit = {
( 31 to 40 ) . foreach( println)
}
}
def main( args: Array[ String ] ) : Unit = {
new Actor1( ) . start( )
new Actor2( ) . start( )
Actor3. start( )
Actor4. start( )
}
}
入门案例-2-发送和接收消息
! 发送异步消息,没有返回值。如Actor1将消息异步发送给Actor2,就认为发生成功,没有返回值 !? 发送同步消息,等待返回值。如Actor1将消息同步发送给Actor2,需要等待Actor2返回结果 !! 发送异步消息,返回值是 Future[Any]。如Actor1将消息异步发送给Actor2,就认为发生成功,返回值会被封装在Future对象中,在未来的某一时刻可以获取到
package cn. hanjiaxiaozhi. actor
import scala. actors. Actor
object ActorDemo2 {
object ActorSender extends Actor{
override def act( ) : Unit = {
ActorReceiver ! "你好!"
ActorReceiver ! "你吃了吗?"
}
}
object ActorReceiver extends Actor{
override def act( ) : Unit = {
receive{
case msg: String => println( "接收到的消息为:" + msg)
}
}
}
def main( args: Array[ String ] ) : Unit = {
ActorSender. start( )
ActorReceiver. start( )
}
}
入门案例-3-循环发送接收消息
上面的程序发送和接收消息只能发一条收一条,无法持续发送和接收,所以接下来可以使用循环来解决
package cn. hanjiaxiaozhi. actor
import scala. actors. Actor
object ActorDemo3 {
object ActorSender extends Actor{
override def act( ) : Unit = {
while ( true ) {
ActorReceiver ! "你好!"
ActorReceiver ! "你吃了吗?"
Thread. sleep( 2000 )
}
}
}
object ActorReceiver extends Actor{
override def act( ) : Unit = {
while ( true ) {
receive{
case msg: String => println( "接收到的消息为:" + msg)
}
}
}
}
def main( args: Array[ String ] ) : Unit = {
ActorSender. start( )
ActorReceiver. start( )
}
}
入门案例-4-复用线程
上面的代码已经实现了循环发生和接收消息 但是使用的是while循环,存在如下的问题:
如果当前Actor没有接收到消息,线程就会处于阻塞状态 如果有很多的Actor,就有可能会导致很多线程都是处于阻塞状态 每次有新的消息来时,重新创建线程来处理 频繁的线程创建、销毁和切换,会影响运行效率 总结:使用while循环会出现线程阻塞效率低! 可以使用Scala封装好的loop+react来复用线程
package cn. hanjiaxiaozhi. actor
import scala. actors. Actor
object ActorDemo4 {
object ActorSender extends Actor{
override def act( ) : Unit = {
while ( true ) {
ActorReceiver ! "你好!"
ActorReceiver ! "你吃了吗?"
Thread. sleep( 2000 )
}
}
}
object ActorReceiver extends Actor{
override def act( ) : Unit = {
loop{
react{
case msg: String => println( "接收到的消息为:" + msg)
}
}
}
}
def main( args: Array[ String ] ) : Unit = {
ActorSender. start( )
ActorReceiver. start( )
}
}
门案例-5-使用样例类发送复杂消息
前面的代码已经差不多可以实现多个Actor之间的交互,但是发送的都是简单的字符串,使用的也只是!异步无返回的形式 那么接下来可以使用样例类来发送复杂的消息并使用多种方式 说明
使用!?来发送同步消息 使用!发送异步无返回消息 使用!!发送异步有返回消息 在Actor的act方法中,可以使用sender获取发送者的Actor引用 返回类型为Future[Any]的对象,Future表示异步返回数据的封装,虽获取到Future的返回值,但不一定有值,可能在将来某一时刻才会返回消息 Future的isSet()可检查是否已经收到返回消息,apply()方法可获取返回数据
! 发送异步消息,没有返回值。如Actor1将消息异步发送给Actor2,就认为发生成功,没有返回值 !? 发送同步消息,等待返回值。如Actor1将消息同步发送给Actor2,需要等待Actor2返回结果 !! 发送异步消息,返回值是 Future[Any]。如Actor1将消息异步发送给Actor2,就认为发生成功,返回值会被封装在Future对象中,在未来的某一时刻可以获取到
package cn. hanjiaxiaozhi. actor
import scala. actors. { ! , Actor, Future}
object ActorDemo5 {
case class SyncMessage( id: Int , msg: String )
case class AsyncMessage( id: Int , msg: String )
case class AsyncMessageAndReply( id: Int , msg: String )
case class ReplyMessage( msg: String , name: String )
object MsgActor extends Actor{
override def act( ) : Unit = {
loop{
react{
case SyncMessage( id, msg) => {
println( s"接收到了同步有返回的消息:id=${id},msg=${msg}" )
Thread. sleep( 5000 )
sender ! ReplyMessage( "不好" , "jack" )
}
case AsyncMessage( id, msg) => {
Thread. sleep( 5000 )
println( s"接收到了异步无返回的消息:id=${id},msg=${msg}" )
}
case AsyncMessageAndReply( id, msg) => {
println( s"接收到了异步有返回的消息:id=${id},msg=${msg}" )
Thread. sleep( 2000 )
sender ! ReplyMessage( "hello" , "jack" )
}
}
}
}
}
def main( args: Array[ String ] ) : Unit = {
MsgActor. start( )
val replyMsg: Any = MsgActor ! ? SyncMessage( 1 , "你好!" )
println( "发送同步有返回消息后收到的回复: " + replyMsg. asInstanceOf[ ReplyMessage] )
println( "同步有返回消息已发送并等待回复......" )
MsgActor ! AsyncMessage( 2 , "你还好吗?" )
println( "异步无返回消息已发送......" )
val future: Future[ Any ] = MsgActor ! ! AsyncMessageAndReply( 3 , "hello!" )
println( "异步有返回消息已发送,后续可以异步出来返回的消息......" )
while ( ! future. isSet) { }
val replyMsg2: ReplyMessage = future. apply( ) . asInstanceOf[ ReplyMessage]
println( "异步返回的消息为:" + replyMsg2)
}
}
Actor-案例-WordCount-掌握-★★★★
需求
之前完成的Scala版的WordCount读取文件夹下的文件做WordCount,最后再做全局的汇总, 整个代码最终虽然可以完成WordCount的计算,但是是一个单线程的! 那么接下来可以使用Actor并发编程来完成多线程版的WordCount 也就是让多个Actor/线程同时计算各个文件中的数据,最后进行汇总得到最终的WordCount结果!
分析
package cn. hanjiaxiaozhi. actor
import java. io. File
import scala. actors. { Actor, Future}
import scala. collection. mutable
import scala. collection. mutable. ListBuffer
import scala. io. Source
case class SubmitTask( path: String )
case class TempResult( tempResultMap: Map[ String , Int ] )
object WordCount {
def main( args: Array[ String ] ) : Unit = {
val futureSet = mutable. Set[ Future[ Any ] ] ( )
val tempResultList = ListBuffer[ Map[ String , Int ] ] ( )
val dir: File = new File( "D:\\scala\\data" )
val files: Array[ File] = dir. listFiles( )
for ( file <- files) {
val actor = new WordCountActor( )
actor. start( )
val path: String = file. getAbsolutePath
val future: Future[ Any ] = actor ! ! SubmitTask( path)
futureSet. add( future)
}
while ( futureSet. filter( _. isSet) . size != futureSet. size) { }
futureSet. foreach( future=> {
val tempResultMap: Map[ String , Int ] = future. apply( ) . asInstanceOf[ TempResult] . tempResultMap
tempResultList. append( tempResultMap)
} )
val tuples: ListBuffer[ ( String , Int ) ] = tempResultList. flatten
val result: Map[ String , Int ] = tuples. groupBy( _. _1) . mapValues( _. map( t=> t. _2) . sum)
println( "最终的结果:" )
result. foreach( println)
}
}
class WordCountActor extends Actor{
override def act( ) : Unit = {
loop{
react{
case SubmitTask( path) => {
println( "接收到了path为" + path)
val lines: Iterator[ String ] = Source. fromFile( path) . getLines( )
val tempResultMap: Map[ String , Int ] = lines. flatMap( _. split( " " ) )
. map( ( _, 1 ) )
. toList
. groupBy( _. _1)
. mapValues( _. length)
println( "局部聚合的结果为:\n" + tempResultMap)
sender ! TempResult( tempResultMap)
}
}
}
}
}