Akka学习笔记(五):Akka与Java的内存模型

Akka学习笔记(五):Akka与Java的内存模型

Akka简化了编写并发软件的过程,本文主要讨论Akka如何在并发应用中访问共享内存。

Java内存模型

Java5之前的JMM是相当混乱的。多线程访问共享内存很有可能会得奇怪的结果,如:

  • 可见性问题,无法及时看到其他线程写入的值
  • 指令乱序,观测到其他线程不可能的行为

从Java 5的JSR 133的实现,很多问题就解决了。JMM是基于一组"happens-before"关联规则,限制了访问内存的行为必须在另一个内存访问行为之前发生。当不想按顺序发生时,可以使用:

  • 监视器锁规则:对一个锁的释放先于所有后续对同一个锁的获取
  • volatile变量规则:对一个volatile变量的写操作先于所有后续对同一个volatile变量的读

JMM看起来很复杂,但是其规范试图在编写高性能,并发数据结构的能力间寻找平衡

Actor与Java内存模型

使用Akka的Actor,有两种方法可以使多线程操作共享内存:

  • 假如一个message被发送给一个actor,在大多数情况下,message是不可变对象,万一message不是不可变的,没有”happen before"规则,receiver可能会看到部分初始化的数据,甚至可能看到无中生有的数据(long/double)
  • 如果一个actor在处理消息时,改变了自己的内部状态,而后又在处理其他消息的时候访问了这个状态。我们需要知道的是,在使用Actor模型时,无法保证同一个线程在处理不同消息时,使用同一个actor(是指一个线程中有多个actor?还是一个actor改变了自己内部的状态后,就不是同一个actor?)

为了防止actor出现可见性问题,执行顺序问题,Akka制定了如下"happen before"规则:

  • 发送规则:一条消息的发送动作先于同一个actor对同一条消息的接收
  • actor后续处理规则:一条消息的处理,优先于同一个actor的下一条消息

两条规则只对同一个actor实例有效

通俗的解释:actor的内部变量(internal fields)是可见的,当下一个消息准备被处理时。所以你的actor的内部变量必须是volatile或者equivalent。

Futures与Java内存模型

一个Future的完成 “先于” 任何注册到它的回调函数的执行。

我们建议不要捕捉(close over)非final的值 (Java中称final,Scala中称val), 如果你 一定 要捕捉非final的值, 它们必须被标记为 volatile 来让它的当前值对回调代码可见。

如果你捕捉一个引用,, 你还必须保证它所指代的实例是线程安全的。 我们强烈建议远离使用锁的对象,因为它们会引入性能问题,甚至可能造成死锁。 这些是使用synchronized的风险。

STM与Java内存模型

Akka中的软件事务性内存 (STM) 也提供了一条 “发生在先” 规则:

  • 事务性引用规则: 在提交过程中对一个事务性引用的成功的写操作先于所有对同一事务性引用的后续读操作发生。

这条规则非常象JMM中的 ‘volatile 变量’ 规则. 目前Akka STM只支持延迟写,所以对共享内存的实际写操作会被延迟到事务提交之时。事务中的写操作被存放在一个本地缓冲区中 (事务的写操作集) ,对其它的事务是不可见的。这就是为什么脏读是不可能的。

这些规则在Akka中的实现会随时间而变化,精确的细节甚至可能依赖于所使用的配置。但是它们是建立在其它的JMM规则如监视器锁规则和volatile变量规则基础上的。 这意味着Akka用户不需要操心为了提供“发生先于”关系而增加同步,因为这是Akka的工作。这样你可以腾出手来处理你的业务逻辑,让Akka框架来保证这些规则的满足。

Actors与共享的可变状态

由于Akka运行在JVM,有些规则仍然必须遵守

  • 捕捉actor内部状态并暴露给其他线程

    class MyActor extends Actor {
    var state = ...
    def receive = {
      case _ =>
        //错误示范
    
      // Very bad, 共享可变状态,
      // will break your application in weird ways
        Future { state = NewState }
        anotherActor ? message onSuccess { r => state = r }
    
      // Very bad, "sender" 随每个消息改变,
      //共享可变状态 bug
        Future { expensiveCalculation(sender()) }
    
        //正确示范
    
      // Completely safe, "self" is OK to close over
      // and it's an ActorRef, which is thread-safe
        Future { expensiveCalculation() } onComplete { f => self ! f.value.get }
    
      // 非常安全,我们捕捉了一个固定值
      // 并且它是一个Actor引用,是线程安全的
        val currentSender = sender()
        Future { expensiveCalculation(currentSender) }
    }
    }
    
  • 消息应该是不可变的,为了避免共享可变状态的陷阱
时间: 2025-01-20 10:06:18

Akka学习笔记(五):Akka与Java的内存模型的相关文章

Akka学习笔记(七):配置

Akka学习笔记(七):配置 使用Akka可以不用任何配置,Akka提供了明智的默认配置.为了适应特别的运行环境,修改默认行为,你可能需要修改: log level and logger backend enable remoting 消息系列化 路由设置 调度器调优 Akka使用Typesafe Config Library,纯java实现的配置库.之前博客有介绍过here 从哪里读取配置 Akka的所有配置信息装在 ActorSystem的实例中, 或者换个说法, 从外界看来, ActorS

Akka学习笔记(一):创建Hello World工程

Akka学习笔记(一):创建Hello World工程 创建工程 使用IDEA,创建SBT工程,在build.sbt中添加akka依赖: name := "My Project" version := "1.0" scalaVersion := "2.10.4" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/relea

Akka学习笔记(二):Actor Systems

Akka学习笔记(二):Actor Systems 图中表示的是一个Actor System,它显示了在这个Actor System中最重要实体之间的关系. 什么是actor,是一个封装了状态和行为的对象,每个actor都通过message交流,从自己的mailbox中读取别的actor发送的消息. 注意: ActorSystem是重量级的对象,会创建1...N个线程,所以一个application一个ActorSystem. 层次结构 假设有一个actor,它的一个功能过于复杂,为了降低复杂度

Akka学习笔记(六):消息传递可靠性

Akka学习笔记(六):消息传递可靠性 一般规则 关于消息发送,有两条基本规则: 最多一次,即不保证消息传递可靠性 message ordering per sender–receiver pair 消息传递机制 最多一次,意味消息有可能丢失 最少一次,保证消息传递可靠,但可能冗余 保证只成功一次,性能最差,消息成功传递,不冗余 为什么不保证传递可靠性 问题是,我们要保证消息传递在什么环节可靠: 消息已经发到网络上了? 消息被远程主机接收到了? 消息已经在接收者actor的邮箱里了? 目标act

Akka学习笔记(四):监督和监控

Akka学习笔记(四):监督和监控 Supervision是什么 supervision表示actors之间的关系.监督者分配任务给下属,因此需要处理反馈的错误.根据不同的错误,监督者supervisor可以做如下操作: 恢复下属,让下属继续运行,继续接收message.当且仅当下属还可以正常运行 重启下属,清空status.一般是第一条情况中,child的异常导致无法正常运行. 关闭下属 关闭自己,向上一级汇报错误 上面的操作都是递归的. 警告 supervision发送的是system me

kvm虚拟化学习笔记(五)之windows虚拟机性能调整

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://koumm.blog.51cto.com/703525/1290682 KVM虚拟化学习笔记系列文章列表 ---------------------------------------- kvm虚拟化学习笔记(一)之kvm虚拟化环境安装http://koumm.blog.51cto.com/703525/1288795 kvm虚拟化学习笔记(二)之linuxkvm虚拟机安装htt

C#可扩展编程之MEF学习笔记(五):MEF高级进阶

好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用的基本已经讲完了,相信大家已经能看出MEF所带来的便利了.今天就介绍一些MEF中一些较为不常用的东西,也就是大家口中的所谓的比较高级的用法. 前面讲的导出都是在每个类上面添加Export注解,实现导出的,那么有没有一种比较简便的方法呢?答案是有的,就是在接口上面写注解,这样只要实现了这个接口的类都会

cmake学习笔记(五)

在cmake 学习笔记(三) 中简单学习了 find_package 的 model 模式,在cmake 学习笔记(四)中了解一个CMakeCache相关的东西.但靠这些知识还是不能看懂PySide使用CMakeLists文件,接下来继续学习find_package的 config 模式及package configure文件相关知识 find_package 的 config 模式 当CMakeLists.txt中使用find_package命令时,首先启用的是 module 模式: 按照 C

Caliburn.Micro学习笔记(五)----协同IResult

Caliburn.Micro学习笔记目录 今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows custom code to execute after the return of a action. /// </summary> public interface IResult { /// <summary> /// Executes the result using the specified context. /