软件事务内存导论(三)用Akka/Multiverse STM实现并发

简介:

用Akka/Multiverse STM实现并发

上面我们已经学习了如何在Clojure里使用STM,我猜你现在一定很好奇如何在Java代码中使用STM。而对于这一需求,我们有如下选择:

  • 直接在Java中使用Clojure STM。方法非常简单,我们只需将事务的代码封装在一个Callable接口的实现中就行了,详情请参见第7章。
  • 喜欢用注解(annotation)的开发者可能会更倾向于使用Multiverse的STM API.
  • 除了STM之外,如果我们计划使用角色(actor),那么还可以考虑选择Akka库。

Multiverse是由Peter Veentjer主持开发的一个基于Java的STM实现。通过这个库,我们可以在Java代码中使用注解来标识事务边界。我们既可以用@TransactionalMethod注解将单个的方法标记为事务性的,也可以用@TransactionalObject注解将一个类的所有方法都标记为事务性的。为了与其他JVM上的语言进行集成,Multiverse还提供了一组丰富的API来控制事物的开始和结束。

Akka是一个由Jonas Boner主持开发的一个基于Scala的解决方案,该方案可以用于包括Java在内的很多其他运行于JVM上的语言。Akka不但提供了STM和基于角色(actor)的并发方案,还提供了将二者混合使用的选项。此外,Akka使用Multiverse作为其STM的实现并提供了ACI(ACID的子集)特性。

Akka的性能非常棒,并且由于它既支持STM又支持基于角色(actor)的模型(详情请参见第8章),本章我们将会用它来实现演示Java STM的例子。


Akka/Multiverse中的事务

Akka的Java版采用了Multiverse的Clojure风格的STM。与Java那繁冗的代码风格相比,Clojure风格的Akka不会强迫我们在能够修改可变实体之前就创建事务。如果我们没有主动提供事务,则Akka/Multiverse就会自动把访问请求封装在一个事务中。所以当我们处于事务之中时,Akka的ref与Clojure的ref的表现是相同的;而当我们位于事务之外时,Akka ref的表现则更像是Clojure的atom。换句话说,想要使变更同步且有序就必须使其在事务中完成,否则变更将是同步但无序的。在任何情况下,Akka都会保证对于ref的更改是原子的、隔离的且一致的,并同时提供了不同等级的协调粒度。

在Akka中,我们既可以用写代码的方式在事务层对事务进行配置,也可以通过配置文件在应用程序/JVM层进行配置。例如,我们可以将一个事务定义为只读(readonly),于是Akka将不再允许任何位于该事务范围内的Akka引用被修改。这样做的好处是,如果我们将一些不可变的事务设置为只读,则程序性能将会得到一定的提升。除此之外,我们还可以控制在冲突情况下事务的最大重试次数。当然,还有很多其他参数可供我们配置,详情请参阅Akka的帮助文档。

Akka扩展了Multiverse中的嵌套事务(请参见6.9节),所以我们能够很方便地在事务中调用启动其他事务的函数。默认情况下,这些内部事务或嵌套事务都是与其外部事务融为一体的。

使用Akka引用和事务

Clojure中的ref是在语言层定义的,而 Akka是一个公共类库所以不能依赖任何现有语言的支持。所以Akka在其akka.stm包中提供了一个托管事务引用(managed transactional reference)Ref和一些为原始类型而设的特殊类,如IntRef、LongRef等。Ref(以及所有原始类型的特化引用)代表指向类型T的一个不可变值的托管可变实体(managed mutable identity)。像Integer、Long、Double、String这些类型以及其他不可变类型都符合作为值对象的(value object)条件。如果我们用了自己定义的类,则必须保证这个类是不可变的。也就是说,这个自定义的类只能包含final字段。

我们可以创建一个Ref的实例作为托管事务引用,其值可以在初始化时指定或干脆不指定(默认为null)。如果想获得引用的当前值,可以使用get()函数。如果要使引用指向另一个可变实体,则可以使用swap()函数。这些调用可以在我们提供的事务里执行,但如果我们没提供事务的话,它们也可以在其各自的事务中运行。

当多个线程都试图更改同一个托管引用时,Akka可以保证只有一个变更可以写入内存而其他变更将全部重做。Akka有专门的事务工具负责管理事务跨越内存栅栏的过程。也就是说,Akka(通过Multiverse)保证了在事务中一个托管ref变更的提交会先于后续所有其他事务对该ref的读操作,即该变更对所有其他事务可见。 

目录
相关文章
|
3月前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
存储 缓存 算法
Go并发调度进阶-GMP和调度器的主要结构,只有接触到底层你才更有底气
Go并发调度进阶-GMP和调度器的主要结构,只有接触到底层你才更有底气
Linux设备驱动中的并发
并发就是多个执行单元或多个进程并行执行,而这多个执行单元对资源进行共享,比如访问同一个变量或同一个硬件资源,这个时候就很容易出现竞态(说简单点就是竞争同一个"女朋友")。
|
算法 Linux
linux多线程同步设计
linux多线程同步设计
170 0
linux多线程同步设计
|
存储 缓存 Java
【Java难点攻克】「NIO和内存映射性能提升系列」彻底透析NIO底层的内存映射机制原理与Direct Memory的关系
【Java难点攻克】「NIO和内存映射性能提升系列」彻底透析NIO底层的内存映射机制原理与Direct Memory的关系
257 0
【Java难点攻克】「NIO和内存映射性能提升系列」彻底透析NIO底层的内存映射机制原理与Direct Memory的关系
二十:从库MTS多线程并行回放(二)(笔记)
一、工作线程执行Event 外部循环 slave_worker_exec_job_group ->pop_jobs_item 出队 获取event 但是不删除 如果队列为空则等待 stage_slave_waiting_event_from_coordinator Wa...
639 0
|
异构计算
《OpenACC并行程序设计:性能优化实践指南》一 3.7 释放主机进程
本节书摘来自华章出版社《OpenACC并行程序设计:性能优化实践指南》一 书中的第3章,第3.7节,作者:[美] 罗布·法伯(Rob Farber),更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1209 0