軟體事務記憶體導論(六)配置Akka事務
宣告:本文是《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 '[email protected]' 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 '[email protected]' because transaction 'DefaultTransaction' is readonly'
[1]近些年來,特別是隨著JVM上新語言的不斷湧現,由Kent Beck所著的《Smalltalk Best Practice Patterns》[Bec96]一書中所討論的一些設計模式又被重新發掘了出來。