軟件事務內存導論(九) 集合與事務
集合與事務
在我們努力學習這些示例的過程中,很容易就會忘記我們所要處理的值都必須是不可變的。隻有實體才是可變的,而狀態值則是不可變的。雖然STM已經為我們減輕了很多負擔,但如果想要在維護不可變性的同時還要兼顧性能的話,對我們來說也將是一個非常嚴峻的挑戰。
為了保證不可變性,我們采取的第一個步驟是將單純用來保存數據的類(value classes)及其內部所有成員字段都置為final(在Scala中是val)。然後,我們需要傳遞地保證我們自己定義的類裏麵的字段所使用的類也都 是不可變的。可以說,將字段和類的定義置為final這一步是整個過程的基礎,這同時也是避免並發問題的第一步。
雖說不可變性可以使代碼變得又好又安全,但是由於性能問題,程序員們還是不大願意使用這一特性。其症結在於,為了維護不可變性,我們可能在數據沒發 生任何變動的情況下也要進行拷貝操作,而這種無謂的拷貝對性能傷害很大。為了解決這個問題,我們在3.6節中曾經討論過持久化數據結構以及如何使用這類數 據結構來減輕程序在性能方麵的負擔。而在持久化數據結構的實現方麵,已經有很多現成的第三方庫可供使用,而Scala本身也提供了這類數據結構。由於 Java也有實現好的持久化數據結構可用,所以我們就無需專門為使用這個特性而去換用自己不熟悉的語言。
除了不可變性之外,我們還希望能獲得一些事務運行所需要的數據結構——這些數據結構的值是不可變的,但其實體可以在托管事務中被改變。Akka提供 了兩種托管數據結構——TransactionalVector和TransactionalMap。這兩種數據結構源自於高效的Scala數據結構,其 工作原理和Java的list、map類似。下麵就讓我們一起來學習如何在Java和Scala中使用TransactionalMap
在Java中使用事務集合類
在Java中使用TransactionalMap是非常簡單的。例如,下麵我們一起來寫一個為運動員們記錄得分的程序,其中對於得分的更新操作是並發執行的。這裏我們將不采用同步或鎖的方式,而是把所有更新操作都放在事務中處理。示例代碼如下所示:
public class Scores { final private TransactionalMap<String, Integer> scoreValues = new TransactionalMap<String, Integer>(); final private Ref<Long> updates = new Ref<Long>(0L); public void updateScore(final String name, final int score) { new Atomic() { public Object atomically() { scoreValues.put(name, score); updates.swap(updates.get() + 1); if (score == 13) throw new RuntimeException("Reject this score"); return null; } }.execute(); } public Iterable<String> getNames() { return asJavaIterable(scoreValues.keySet()); } public long getNumberOfUpdates() { return updates.get(); } public int getScore(final String name) { return scoreValues.get(name).get(); } }
在updateScore()函數中,我們把設置某個運動員的得分以及增加更新次數的操作都收斂到一個事務裏麵,該事務中所用到的 TransactionalMap類型的scoreValue字段以及Ref類型updates字段都是托管類型。其中TransactionalMap 支持普通Map的所有函數,隻不過這些函數都是事務性的——即一旦事務回滾,我們對其進行的任何變更都將被丟棄。為了能夠觀察到實際的效果,我們人為地設 置了一個回滾條件,即當得分為13的時,我們會先完成變更操作,然後拋異常令事務回滾。
在Java中,如果集合類實現了Iterable接口的話,我們就可以使用像for(String name: collectionOfNames)這樣的for-each語句。但TransactionalMap是一個Scala集合類,並且沒有直接支持這個接 口。別擔心——Scala提供了一個叫做javaConversions的門麵(façade設計模式——譯者注),該門麵提供了很多方便的函數來獲取我 們想要的Java接口。例如,我們可以使用asJavaIterable()函數來獲取原本需要使用getNames()函數才能拿到的接口。
至此我們已經完成了Scores類的全部功能,接下來我們還需要寫一個測試用例來檢驗Scores類所實現的這些功能:
package com.agiledeveloper.pcj; public class UseScores { public static void main(final String[] args) { final Scores scores = new Scores(); scores.updateScore("Joe", 14); scores.updateScore("Sally", 15); scores.updateScore("Bernie", 12); System.out.println("Number of updates: " + scores.getNumberOfUpdates()); try { scores.updateScore("Bill", 13); } catch(Exception ex) { System.out.println("update failed for score 13"); } System.out.println("Number of updates: " + scores.getNumberOfUpdates()); for(String name : scores.getNames()) { System.out.println( String.format("Score for %s is %d", name, scores.getScore(name))); } } }
上例中,我們先是添加了三個正常的運動員成績,隨後又增加了一個可以導致事務回滾的成績。但由於事務的存在,所以最後一個成績更新操作最終是無效的。而在代碼的最後,我們會遍曆並輸出事務性map裏麵的所有數據。下麵讓我們觀察一下這段代碼的輸出結果:
Number of updates: 3 update failed for score 13 Number of updates: 3 Score for Joe is 14 Score for Bernie is 12 Score for Sally is 15
在Scala中使用事務集合類
在Scala中,我們可以用與Java類似的方式來使用事務集合類。隻不過由於這次是在Scala中,所以這裏我們需要使用Scala的內部迭代器而不是javaConversions門麵(facade)。下麵讓我們把Scores類翻譯成Scala代碼:
class Scores { private val scoreValues = new TransactionalMap[String, Int]() private val updates = Ref(0L) def updateScore(name : String, score : Int) = { atomic { scoreValues.put(name, score) updates.swap(updates.get() + 1) if (score == 13) throw new RuntimeException("Reject this score") } } def foreach(codeBlock : ((String, Int)) => Unit) = scoreValues.foreach(codeBlock) def getNumberOfUpdates() = updates.get() }
如上所示,updateScore()函數與Java版本基本是相同的。唯一有點區別的地方是,我們去掉了getNames()函數和 getScore()函數,並為foreach()提供了內部迭代器來遍曆map中的數據。我們在下麵所列出了Scala版UseScores類的實現, 這段代碼是其Java版代碼的直譯:
package com.agiledeveloper.pcj object UseScores { def main(args : Array[String]) : Unit = { val scores = new Scores() scores.updateScore("Joe", 14) scores.updateScore("Sally", 15) scores.updateScore("Bernie", 12) println("Number of updates: " + scores.getNumberOfUpdates()) try { scores.updateScore("Bill", 13) } catch { case ex => println("update failed for score 13") } println("Number of updates: " + scores.getNumberOfUpdates()) scores.foreach { mapEntry => val (name, score) = mapEntry println("Score for " + name + " is " + score) } } }
不出所料,測試用例的輸出結果也與Java版代碼如出一轍:
Number of updates: 3 update failed for score 13 Number of updates: 3 Score for Joe is 14 Score for Bernie is 12 Score for Sally is 15
文章轉自 並發編程網-ifeve.com
最後更新:2017-05-22 16:02:13