edwardsbean 2014-08-07 1675浏览量
Akka简化了编写并发软件的过程,本文主要讨论Akka如何在并发应用中访问共享内存。
Java5之前的JMM是相当混乱的。多线程访问共享内存很有可能会得奇怪的结果,如:
从Java 5的JSR 133的实现,很多问题就解决了。JMM是基于一组"happens-before"关联规则,限制了访问内存的行为必须在另一个内存访问行为之前发生。当不想按顺序发生时,可以使用:
JMM看起来很复杂,但是其规范试图在编写高性能,并发数据结构的能力间寻找平衡
使用Akka的Actor,有两种方法可以使多线程操作共享内存:
为了防止actor出现可见性问题,执行顺序问题,Akka制定了如下"happen before"规则:
两条规则只对同一个actor实例有效
通俗的解释:actor的内部变量(internal fields)是可见的,当下一个消息准备被处理时。所以你的actor的内部变量必须是volatile或者equivalent。
一个Future的完成 “先于” 任何注册到它的回调函数的执行。
我们建议不要捕捉(close over)非final的值 (Java中称final,Scala中称val), 如果你 一定 要捕捉非final的值, 它们必须被标记为 volatile 来让它的当前值对回调代码可见。
如果你捕捉一个引用,, 你还必须保证它所指代的实例是线程安全的。 我们强烈建议远离使用锁的对象,因为它们会引入性能问题,甚至可能造成死锁。 这些是使用synchronized的风险。
Akka中的软件事务性内存 (STM) 也提供了一条 “发生在先” 规则:
这条规则非常象JMM中的 ‘volatile 变量’ 规则. 目前Akka STM只支持延迟写,所以对共享内存的实际写操作会被延迟到事务提交之时。事务中的写操作被存放在一个本地缓冲区中 (事务的写操作集) ,对其它的事务是不可见的。这就是为什么脏读是不可能的。
这些规则在Akka中的实现会随时间而变化,精确的细节甚至可能依赖于所使用的配置。但是它们是建立在其它的JMM规则如监视器锁规则和volatile变量规则基础上的。 这意味着Akka用户不需要操心为了提供“发生先于”关系而增加同步,因为这是Akka的工作。这样你可以腾出手来处理你的业务逻辑,让Akka框架来保证这些规则的满足。
由于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) }
}
}
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
集结各类场景实战经验,助你开发运维畅行无忧