Akka与Java内存模型

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

    • 一个线程看不到其它线程所写入的值:可见性问题
    • 由于指令没有按期望的顺序执行,一个线程观察到其它线程的 ‘不可能’ 行为:指令重排序问题

    随着Java 5中JSR 133的实现,很多这种问题都被解决了。 JMM是一组基于 “发生在先” 关系的规则, 限制了一个内存访问行为何时必须在另一个内存访问行为之前发生,以及反过来,它们何时能够不按顺序发生。这些规则的两个例子包括:

    • 监视器锁规则: 对一个锁的释放先于所有后续对同一个锁的获取
    • volatile变量规则: 对一个volatile变量的写操作先于所有对同一volatile变量的后续读操作

    虽然JMM看起来很复杂,但是其规范试图在易用性和编写高性能、可扩展的并发数据结构的能力之间寻找一个平衡。

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

    • 如果一个actor在处理某条消息时改变了自己的内部状态,而之后又在处理其它消息时又访问了这个状态。一条很重要的需要了解的规则是,在使用actor模型时你无法保证,同一个线程会在处理不同的消息时使用同一个actor。
    • actor发送规则 : 一条消息的发送动作先于目标actor对同一条消息的接收。
    • actor后续处理规则: 对同一个actor,一条消息的处理先于下一条消息处理

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

    一个Future的完成 “先于” 任何注册到它的回调函数的执行。

    如果你捕捉一个引用,你还必须保证它所指代的实例是线程安全的。我们强烈建议远离使用锁的对象,因为它们会引入性能问题,甚至最坏可能造成死锁。这些是使用synchronized的风险。

    Akka中的软件事务性内存 (STM) 也提供了一条 “发生在先” 规则:

    • 事务性引用规则: 对一个事务性引用,在提交过程中一次成功的写操作,先于所有对同一事务性引用的后续读操作发生。

    这条规则非常象JMM中的“volatile 变量”规则。目前Akka STM只支持延迟写,所以对共享内存的实际写操作会被延迟到事务提交之时。在事务中发生的写操作会被存放在一个本地缓冲区内 (事务的写操作集) ,并且对其它事务是不可见的。这就是为什么脏读是不可能的。

    这些规则在Akka中的实现会随时间而变化,精确的细节甚至可能依赖于所使用的配置。但是它们是建立在其它JMM规则之上的,如监视器锁规则、volatile变量规则。 这意味着Akka用户不需要操心为了提供“发生先于”关系而增加同步,因为这是Akka的工作。这样你可以腾出手来处理业务逻辑,让Akka框架来保证这些规则的满足。

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