akka学习之java内存模型JMM

     使用类型安全平台(包括scala和akka在内)的一个目的是,它能使编写并发软件变得简单。本章主要介绍类型安全平台,特别是akka,是如何在并发应用中处理共享内存的。

 

java内存模型JMM

       在jdk 5之前的版本中,java内存模型的定义并不是很好。当多线程访问共享内存时,可能会得到各种千奇百怪的结果,比如:

       看见性问题:一个线程看不到另一个线程写入的值。

       指令重排序问题:一个线程可能看到另一个线程完全不可能的行为。因为有些机器指令并不是按照你所以期望的顺序执行。

      在java 5之后,很多这样的问题都得到了解决。JMM的规则是基于“happens-before”关系的。这个关系限制了什么时候一个内存访问发生在另一个之前,反过来,也限制了何时允许不按顺序访问。下面是这些规则中的两个例子:

1.监视器锁规则:在其他线程能获取相同的锁之前,这个锁必须先被释放。

2.volatile变量规则:在同时对同一个volatile变量进行读写访问时,写访问必须优先于读访问。

      JMM看起来比较复杂,但是它的主要思想是想再使用方便和编写高性能、可扩展的并发数据结构之间寻找一个平衡点。

 

Actor和JMM

     针对akka中actor的实现,多线程有两种方法使用到共享内存:

1.如果一个消息从一个actor发送到另一个actor,在大多数情况下,消息读都是不可变的,但是如果消息不适合构造为不可变对象,如果没有“happen-before”规则,那么接收到的消息对象可能是没有构造完全的对象,甚至根本看不到消息的值。

2.如果处理一条消息时改变了actor的内部状态,稍后处理的另一条消息可能需要访问这个状态。因此必须注意:在actor模型中,你不能保证同一个线程会处理统一个actor的不同消息。

      为了防止actor中的可见性问题和重排序问题,akka确保了下面“happen-before”规则:

1.actor发送规则:对同一个actor,发送消息先于接收消息。

2.actor消息顺序处理规则:在同一个actor中,上一条消息的处理先于下一条消息的处理。

     注意:一般说来,上一条消息导致的actor内部字段的变化,对于actor处理的下一条消息来说是可见的,因此不需要把actor的字段声明为volatile或者等效(equaivalent)的。另外,上面的两天规则是针对同一个actor来说的,对不同的actor无效。

 

Futures 和JMM

       任何注册在future上回调必须在Future完成之后执行。建议不要过多使用非常量字段,如果你一定要选择非常量字段,你必须使用volatile,以便字段的当前值能对回调可见。如果你过多使用引用,你必须确保这些引用的实例时线程安全的。强烈建议不要使用带锁的对象,以防引入性能问题甚至死锁。

 

STM和JMM

      akka的软件事务内存(STM)也同样支持“happen-before”规则:

      STM规则:在事务引用中,一个提交成功的写操作必须在所有的读操作之前。

      这个规则有点像JMM中volatile变量,不过当前akkaSTM仅支持延迟写操作,因此实现上共享内存的写操作被延迟到事务提交的时候。事务期间的写数据放在本地buffer中(事务写集合),这个buffer对其他事务不可见。这也保证了不会发生脏读现象。

       这些规则的实现细节可能随着版本的不同而改变,甚至具体的细节还可能依赖于配置。但是他们会建造向监视器锁和volatile变量规则一样的其他规则。这意味着akka的使用者不必担心需要自己增加同步模块来支持“happen-before”关系,这是akka负责的工作。你仅仅需要把精力放在你的业务逻辑上,akka框架保证这些规则跟你预期的一模一样。

 

actor和共享可变状态

     毕竟akka是运行在JVM上的,所以还是要遵守一些规则的。下面这个例子是使用actor内部状态,并暴露给其他线程:

 

class MyActor extends Actor {
 var state = ...
 def receive = {
    case _ =>
      //错误
 
    // 相当的糟糕,共享可变状态
    // 会使你的应用出现奇怪的行为
      Future { state = NewState }
      anotherActor ? message onSuccess { r => state = r }
 
    // 相当糟糕,“sender”会因为每条消息而改变
    // 共享可变状态BUG
      Future { expensiveCalculation(sender()) }
 
      //正确
 
    // 完全安全,“self”可以封装
    // and it's an ActorRef, which is thread-safe
      Future { expensiveCalculation() } onComplete { f => self ! f.value.get }
 
    // 完全安全,我们封装一个固定值
    // 而且它是一个ActorRef,这个对象是线程安全的
      val currentSender = sender()
      Future { expensiveCalculation(currentSender) }
 }
}

 

 消息必须是不可变的,这样可以避免共享可变状态的陷阱。

 

 

 

 

 

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/iteye_9153/article/details/82611567
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞