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

简介: <div style="margin:0px; padding:0px; border:0px; line-height:1.6; font-family:'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif; font


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) }
    }
    }
    
  • 消息应该是不可变的,为了避免共享可变状态的陷阱
目录
相关文章
|
25天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
2月前
|
缓存 easyexcel Java
Java EasyExcel 导出报内存溢出如何解决
大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!
173 1
|
6天前
|
Java
java内存区域
1)栈内存:保存所有的对象名称 2)堆内存:保存每个对象的具体属性 3)全局数据区:保存static类型的属性 4)全局代码区:保存所有的方法定义
17 1
|
20天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
40 6
|
24天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
37 2
|
25天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
61 1
|
1月前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
32 1
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
2月前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。
|
2月前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
43 2