声明:本文是《Java虚拟机并发编程》的第六章,感谢华章出版社授权并发编程网站发布此文,禁止以任何形式转载此文。
配置Akka事务
默认情况下,Akka为其相关的运行参数都设定了默认值,我们可以通过代码或配置文件akka.conf来更改这些默认设置。如果想了解如何指定或修改该配置文件位置的详细信息,请参阅Akka的文档。
针对单个事务,我们可以利用TransactionFactory在程序代码中更改其设置。下面就让我们用这种方式先后在Java和Scala中更改一些设置来为你展示如何实现设置的变更。
在Java中对事务进行配置
public class CoffeePot { private static final Ref<Integer> cups = new Ref<Integer>(24); public static int readWriteCups(final boolean write) { final TransactionFactory factory = new TransactionFactoryBuilder().setReadonly(true).build(); return new Atomic<Integer>(factory) { public Integer atomically() { if(write) cups.swap(20); return cups.get(); } }.execute(); }
为了能够用编程的方式对事务进行配置,我们需要一个TransactionFactory实例对象,而TransactionFactoryBuilder则为我们提供了很多方便的函数用于创建该Factory。在上例中,我们创建了一个TranactionFactoryBuilder实例对象,并调用该对象的setReadonly()函数来为TransactionFactory添加readonly选项。由于TransactionFactoryBuilder实现了Cascade[1]设计模式,所以我们可以将更多用于改变事务属性的函数串在一起挂在TransactionFactoryBuilder构造函数之后、build()函数之前。随后我们把factory的实例对象作为Atomic的一个构造函数参数传给它,这样就保证了该事务内的所有操作都不会变更任何托管引用。
通过上述设置我们已经将readWriteCups()变成了一个只读事务,接下来你肯定希望了解在一个只读事务中试图改变引用的值将会产生什么后果。下面我们会调用两次readWriteCups(),第一次仅仅是读取cups引用的内容,而第二次调用则会尝试改变cups引用的值。
public static void main(final String[] args) { System.out.println("Read only"); readWriteCups(false); System.out.println("Attempt to write"); try { readWriteCups(true); } catch(Exception ex) { System.out.println("Failed " + ex); } } }
由于被设置成了只读,所以readWriteCups()事务不欢迎变更请求。于是当我们试图更改cups引用的值时,系统抛出了org.multiverse.api.exceptions.ReadonlyException异常,并且整个事务也将回滚。
Read only Attempt to write Failed org.multiverse.api.exceptions.ReadonlyException: Can't open for write transactional object 'akka.stm.Ref@1272670619' because transaction 'DefaultTransaction' is readonly'
上述运行时异常是在调用引用的swap()的时候抛出来的。该函数的作用是当且仅当新值与当前值不同时,将其引用改为指向新值的地址;否则,该函数将忽略变更请求。所以在本例中,如果我们在调用swap()时将参数20换成与当前cpus引用的值相等的24,则系统就不会抛出任何异常。
在Scala中对事物进行配置
在Scala中,我们可以使用atomic()函数代替Atomic类来创建事务,该函数在使用时需要一个TransactionFactory类型的可选参数。同时,由于我们能够在伙伴对象(companion object)上使用工厂方法,所以创建factory实例也比在Java中要简单许多。
object CoffeePot { val cups = Ref(24) def readWriteCups(write : Boolean) = { val factory = TransactionFactory(readonly = true) atomic(factory) { if(write) cups.swap(20) cups.get() } } def main(args : Array[String]) : Unit = { println("Read only") readWriteCups(false) println("Attempt to write") try { readWriteCups(true) } catch { case ex => println("Failed " + ex) } } }
除了在代码方面保持了Scala和Akka特有的简洁优雅之外,上述代码与Java版本就没有什么其他不同之处了,所以代码的执行结果也毫无意外地和Java版本完全相同。
Read only Attempt to write Failed org.multiverse.api.exceptions.ReadonlyException: Can't open for write transactional object 'akka.stm.Ref@1761506447' because transaction 'DefaultTransaction' is readonly'
[1]近些年来,特别是随着JVM上新语言的不断涌现,由Kent Beck所著的《Smalltalk Best Practice Patterns》[Bec96]一书中所讨论的一些设计模式又被重新发掘了出来。
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/140969.html