Akka手册译(一)——Akka和Java内存模型
2017-01-01 本文已影响0人
儿哥欠三百首
使用Lightbend平台的主要好处,包括Scala和Akka,它简化了编写并发软件的过程。本文讨论如何Lightbend平台,尤其是Akka,并发应用程序共享内存的方法。
Java内存模型
在Java 5之前,Java内存模型(JMM)有误的定义。由多个线程访问共享内存时可以得到各种奇怪的结果,如:
- 线程看不到其它线程写入的值:可见性问题;
- 线程观察其他线程的“不可能”行为,指令没有按预期的顺序执行引起:指令重新排序的问题。
Java 5的JSR 133解决了这些问题。JMM的一组规则是基于“发生前”的关系,这强制了当一个内存访问发生在其它之前,相反地,允许在出现故障时发生。两个例子说明这些规则: - 监视锁规则:释放一个锁之前每一个后继得到相同的锁。
- 易失变量规则:写易失性变量之前每一个后续的读相同的易失性变量。
尽管JMM看起来复杂,规范试图找到一个易于使用和写的能力之间的平衡性能,可伸缩的并发数据结构。
Actor和Java内存模型
Akka中Actor的实现,有两种方法在共享内存环境下执行多线程行为:
- 如果一个消息发送到一个Actor(如由另一个Actor)。在大多数情况下,消息是不可变的,但是如果这个信息构造不可变的对象是不正确的,没有“发生前”的规则,接收者可能部分初始化数据结构,甚至可能值是凭空捏造的(长型/双精度型)
- 如果Actor在处理消息时改变内部状态,并在片刻后访问这个状态在处理另一个消息时。深刻认识到Actor模型中并不保证,同样的线程会对不同消息执行相同的Actor。
为了避免Actor的可见性和重排序问题,Akka保证了下例两个“发生前”的规则: - Actor发送规则:一个Actor发送消息发生之前由通一个Actor接收消息。
- Actor后续处理规则:在处理消息发生前由同一个Actor处理后续消息。
注意
通俗的讲改变Actor的内部字段在下一个消息的可见。Actor中的字段不能是易失或相价的。
两个规则仅适用于同一个Actor实例,对不同的Actor无效。
Futures和Java模型
完成Feature的“发生前”执行调用的回调注册。
我们建议不要封闭非final字段(在Java用final,在Scala中用val )如果你选择封闭非final字段,它们必须被标识为‘volatile’为了字段的当前值是可见的回调。
如果封闭一个引用,需要确保实例是线程安全的。我们强烈建议远离使用锁定的对象,因为它在最坏的情况下,引入性能问题和死锁。这样的同步是危险的。
Actor和共享可变状态
由于Akka运行在JVM上仍有一些规则要遵循。
- 封闭Actor内部状态,并向其它线程暴露它。
1. class MyActor extends Actor {
2. var state = ...
3. def receive = {
4. case _ =>
5. //Wrongs
6.
7. // Very bad, shared mutable state,
8. // will break your application in weird ways
9. Future { state = NewState }
10. anotherActor ? message onSuccess { r => state = r }
11.
12. // Very bad, "sender" changes for every message,
13. // shared mutable state bug
14. Future { expensiveCalculation(sender()) }
15.
16. //Rights
17.
18. // Completely safe, "self" is OK to close over
19. // and it's an ActorRef, which is thread-safe
20. Future { expensiveCalculation() } onComplete { f => self ! f.value.get }
21.
22. // Completely safe, we close over a fixed value
23. // and it's an ActorRef, which is thread-safe
24. val currentSender = sender()
25. Future { expensiveCalculation(currentSender) }
26. }
27.}
- 消息应该是不可变的,这是为了避免共享可变状态的陷阱。