scala--Actor并发编程-了解

Actor概述

  • 接收消息
    • Actor中使用receive方法来接收消息,需要给receive方法传入一个偏函数
  • 注意:receive方法只接收一次消息,接收完后继续执行act方法
{

    case 变量名1:消息类型1 => 业务处理1,

    case 变量名2:消息类型2 => 业务处理2,

    ...

}
  • 总结
    • 1.继承Actor,重写act()
    • 2.发送的sender的act方法中:
      • 例: ActorReceiver ! “你好!”
    • 3.接收的receiver的act方法中
  • 例:
    receive{

    //其实就是一个模式匹配

 case msg:String => println("接收到的消息为:"+msg)

  }
  • 4.class定义的Actor,需要new之后start
    • object定义的Actor可以直接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
  • 如果不行得使用方式二
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

方式二:

  • 重新创建一个项目,引入scalaSDK
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

方式三:

  • 直接把scala的jar包拷贝到idea中了

入门案例-1-创建Actor

  • 类似于Java中创建线程
package cn.hanjiaxiaozhi.actor
​
import scala.actors.Actor
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 11:45
 * Desc 演示Actor入门案例1-创建Actor
 */
object ActorDemo1 {
  //类似于创建了一个Thread,重新run方法
  //创建一个线程打印1~10
  class Actor1 extends Actor{
    override def act(): Unit = {
      (1 to 10).foreach(println)
      /*for(i <- 1 to 10){
        println(i)
      }*/
    }
  }
  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 = {
    //class定义的Actor,需要new之后start
    //object定义的Actor可以直接start
    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
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 14:15
 * Desc 演示Actor入门案例2-发送接收消息
 */
object ActorDemo2 {
  //发送线程
  object ActorSender extends Actor{
    override def act(): Unit = {
      //给ActorReceiver发送一条消息
      ActorReceiver ! "你好!"
      //给ActorReceiver再发送一条消息,如果接收线程没有一直运行那么收不到该条
      ActorReceiver ! "你吃了吗?"}
  }//接收线程
  object ActorReceiver extends Actor{
    override def act(): Unit = {
      //receive方法可以一直等待接收消息
      //下面的写法其实就是一个模式匹配省略了match而已
      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
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 14:15
 * Desc 演示Actor入门案例3-循环发送接收消息
 */
object ActorDemo3 {
  //发送线程
  object ActorSender extends Actor{
    override def act(): Unit = {
      while(true){
        //给ActorReceiver发送一条消息
        ActorReceiver ! "你好!"
        //给ActorReceiver再发送一条消息,如果接收线程没有一直运行那么收不到该条
        ActorReceiver ! "你吃了吗?"
        Thread.sleep(2000)//每次发完间隔2s
      }
    }
  }//接收线程
  object ActorReceiver extends Actor{
    override def act(): Unit = {
      //会一直执行while循环,就可以一直接收消息
      while(true){
        //receive方法可以一直等待接收消息
        //下面的写法其实就是一个模式匹配省略了match而已
        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
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 14:15
 * Desc 演示Actor入门案例4-使用loop+react复用线程提高效率
 */
object ActorDemo4 {
  //发送线程
  object ActorSender extends Actor{
    override def act(): Unit = {
      while(true){
        //给ActorReceiver发送一条消息
        ActorReceiver ! "你好!"
        //给ActorReceiver再发送一条消息,如果接收线程没有一直运行那么收不到该条
        ActorReceiver ! "你吃了吗?"
        Thread.sleep(2000)//每次发完间隔2s
      }
    }
  }//接收线程
  object ActorReceiver extends Actor{
    override def act(): Unit = {
      //会一直执行循环,就可以一直接收消息
      /*while(true){
        //receive方法可以一直等待接收消息
        //下面的写法其实就是一个模式匹配省略了match而已
        receive{
          //其实就是一个模式匹配
          case msg:String => println("接收到的消息为:"+msg)
        }
      }*/
      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}/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 14:15
 * Desc 演示Actor入门案例5-使用样例类发送复杂消息并演示各种发送方式
 * !?发送同步消息,等待返回值。如Actor1将消息同步发送给Actor2,需要等待Actor2返回结果
 * !发送异步消息,没有返回值。如Actor1将消息异步发送给Actor2,就认为发生成功,没有返回值
 * !!发送异步消息,返回值是 Future[Any]。如Actor1将消息异步发送给Actor2,就认为发生成功,返回值会被封装在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)//回复的消息//使用MsgActor线程不断接收main线程发送的消息
  object MsgActor extends Actor{
    override def act(): Unit = {
      loop{
        react{
          //同步有返回
          case SyncMessage(id,msg) => {
            println(s"接收到了同步有返回的消息:id=${id},msg=${msg}")
            Thread.sleep(5000)//收到消息后2s后再返回
            //使用sender表示返回消息
            sender ! ReplyMessage("不好","jack")
          }
          //异步无返回
          case AsyncMessage(id,msg) => {
            Thread.sleep(5000)//收到消息后2s再打印
            println(s"接收到了异步无返回的消息:id=${id},msg=${msg}")
          }
          //异步有返回
          case AsyncMessageAndReply(id,msg) => {
            println(s"接收到了异步有返回的消息:id=${id},msg=${msg}")
            Thread.sleep(2000)//收到消息后2s后再返回
            sender ! ReplyMessage("hello","jack")
          }
        }
      }
    }}
​
​
  //使用main线程给MsgActor分别发送同步有返回,异步无返回和异步有返回的消息
  def main(args: Array[String]): Unit = {
    //注意:main方法一运行main线程就开启了
    //但是MsgActor线程得单独开启
    MsgActor.start()//1.同步有返回--同步的表示要等待返回
    val replyMsg: Any = MsgActor !? SyncMessage(1,"你好!")
    println("发送同步有返回消息后收到的回复: " + replyMsg.asInstanceOf[ReplyMessage])
    println("同步有返回消息已发送并等待回复......")//2.异步无返回--异步发送无需等待!
    MsgActor ! AsyncMessage(2,"你还好吗?")
    println("异步无返回消息已发送......")//3.异步有返回--异步发送无需等待且后续可以根据future获取异步返回的结果
    //future表示异步返回的结果,后续可以通过isSet方法判断是否被已经返回
    val future: Future[Any] = MsgActor !! AsyncMessageAndReply(3,"hello!")
    println("异步有返回消息已发送,后续可以异步出来返回的消息......")
    while(!future.isSet){}
    //注意:future.apply()表示从future对象中取出返回的消息
    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
​
/**
 * Author hanjiaxiaozhi
 * Date 2020/7/19 15:28
 * Desc 演示使用Scala的Actor-API完成多线程版的WordCount
 *///定义一些样例类用来封装消息,等一下写的是Actor程序,得要进行消息传递
//如分配任务给Actor时要告诉它读取哪个文件?
//Actor计算出这个文件的WordCount结果(局部结果)需要发送给main进行最终的汇总(全局汇总)
case class SubmitTask(path: String)case class TempResult(tempResultMap: Map[String, Int])object WordCount {
  def main(args: Array[String]): Unit = {
    //0.准备一个可变的集合用来存放异步返回的结果
    val futureSet = mutable.Set[Future[Any]]()
    //0.准备一个可变集合用来存放已经真正返回的结果
    val tempResultList = ListBuffer[Map[String, Int]]()//1.读取文件夹
    val dir: File = new File("D:\\scala\\data")
    //列出文件夹下的文件
    val files: Array[File] = dir.listFiles()
    //遍历文件
    for(file <- files){
      //2.开启Actor任务,并发送文件名给Actor进行WordCount计算
      val actor = new WordCountActor()
      actor.start()
      //有一个文件进来就会创建一个actor并开启任务
      //获取文件绝对路径
      val path: String = file.getAbsolutePath
      //3.发送异步有返回消息
      val future: Future[Any] = actor !! SubmitTask(path)
      //上面的是异步发送,特点是只要一执行,立马返回future,然后接着执行后面的(也就是下一次循环)
      //将future放入到准备好的Set中
      futureSet.add(future)
    }//上面的for循环执行完之后,所有的文件WordCount任务就已经被分配到各个Actor了
    //并且异步返回的结果已经在futureSet中了
    //但是futureSet中存放的future对象是一个异步返回结果
    //表示未来某个时刻会真正的将计算完的结果放到future中,可以是使用isSet进行判断
    //isSet可以理解为是否已经将执行的结果设置回future中了,不表示判断是否是集合
    //isSet返回true表示异步返回的结果已经ok,可以取出来了
    //那么现在应该得等待所有的Actor都计算完了,然后就可以做全局汇总了
    //过滤出futureSet中已经ok的并统计数量
    //为了大家能看懂拆开来写的,但是最后判断的时候的写到一起去
    //因为要让下面的代码不断的每次都得执行
    //val okNum: Int = futureSet.filter(_.isSet).size //已经真正计算完的ok的数量
    //val allNum: Int = futureSet.size
    //如果okNum不等于allNum,说明futureSet中还有未完成计算的Actor那么执行{}空循环指导okNum == allNum
    //while (okNum != allNum) {}
    while (futureSet.filter(_.isSet).size != futureSet.size) {}
    /*import scala.util.control.Breaks._
    breakable {
      while (true) {
        if (okNum == allNum) {
          break() //scala对break关键字不支持!得使用其他方式,Scala也不建议!
        }
      }
    }*/
    //如果代码能走到这一行,说明okNum == allNum,也就是futureSet中所有的futrure异步返回对象都已经ok了有结果了
    futureSet.foreach(future=>{
      //取出来的tempResultMap就是每一个文件的WordCount局部结果
      //apply表示从future中取出结果
      //.asInstanceOf[TempResult]表示转成真正返回类型
      //.tempResultMap表示从样例类中取出map
      val tempResultMap: Map[String, Int] = future.apply().asInstanceOf[TempResult].tempResultMap
      tempResultList.append(tempResultMap)//加入到准备好的List集合中
    })
    //代码如果走到这里说明,所有的文件的局部聚合结果已经存放到了tempResultList中
    //那么接下来就是对tempResultList进行全局汇总了
    //val tempResultList = ListBuffer[Map[String, Int]]()
    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)
          //4.读取path对应的文件进行WordCount并返回
          val lines: Iterator[String] = Source.fromFile(path).getLines()
          val tempResultMap: Map[String, Int] = lines.flatMap(_.split(" ")) //切割
            .map((_, 1)) //每个单词记为1
            .toList
            .groupBy(_._1) //按照单词进行分组
            .mapValues(_.length)//计数
          println("局部聚合的结果为:\n"+tempResultMap)
          //给main返回结果
          sender ! TempResult(tempResultMap)
        }
      }
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值