Java高并发高性能编程(多线程,协程,Actor,RxJava、Akka、Reactor)

Akka与Java内存模型

2018-04-28  本文已影响20人  jiangmo

Java内存模型

在Java 5之前,Java内存模型(JMM)定义是有问题的。当多个线程访问共享内存时很可能得到各种奇怪的结果,例如:

一个线程看不到其它线程所写入的值:可见性问题
由于指令没有按期望的顺序执行,一个线程观察到其它线程的 ‘不可能’ 行为:指令重排序问题
随着Java 5中JSR 133的实现,很多这种问题都被解决了。 JMM是一组基于 “发生在先” 关系的规则, 限制了一个内存访问行为何时必须在另一个内存访问行为之前发生,以及反过来,它们何时能够不按顺序发生。这些规则的两个例子包括(还有好几条规则):

Actors与Java内存模型

使用Akka中的Actor实现,有两种方法让多个线程对共享的内存进行操作:

为了避免actor中的可见性和重排序问题,Akka保证以下两条 “发生在先” 规则:

通俗地说,这意味着当这个actor处理下一个消息的时候,对actor的内部字段的改变是可见的。因此,在你的actor中的域不需要是volitale或是同等可见性的。

这两条规则都只应用于同一个actor实例,对不同的actor则无效

Future与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框架来保证这些规则的满足。

Actor与共享的可变状态

因为Akka运行在JVM上,所以还有一些其它的规则需要遵守。

捕捉Actor内部状态并暴露给其它线程

class MyActor extends Actor {
  var state = ...
  def receive = {
    case _ =>
      // 错误的做法

      // 非常错误,共享可变状态,
      // 会让应用莫名其妙地崩溃
      Future { state = NewState }
      anotherActor ? message onSuccess { r => state = r }

      // 非常错误, 共享可变状态 bug
      // "发送者"是一个可变变量,随每个消息改变
      Future { expensiveCalculation(sender) }

      //正确的做法

      // 非常安全, "self" 被闭包捕捉是安全的
      // 并且它是一个Actor引用, 是线程安全的
      Future { expensiveCalculation() } onComplete { f => self ! f.value.get }

      // 非常安全,我们捕捉了一个固定值
      // 并且它是一个Actor引用,是线程安全的
      val currentSender = sender
      Future { expensiveCalculation(currentSender) }
  }
}

消息应当是不可变的, 这是为了避开共享可变状态的陷阱。

上一篇下一篇

猜你喜欢

热点阅读