在我們努力學習這些示例的過程中,很容易就會忘記我們所要處理的值都必須是不可變的。隻有實體才是可變的,而狀态值則是不可變的。雖然stm已經為我們減輕了很多負擔,但如果想要在維護不可變性的同時還要兼顧性能的話,對我們來說也将是一個非常嚴峻的挑戰。
為了保證不可變性,我們采取的第一個步驟是将單純用來儲存資料的類(value classes)及其内部所有成員字段都置為final(在scala中是val)。然後,我們需要傳遞地保證我們自己定義的類裡面的字段所使用的類也都是不可變的。可以說,将字段和類的定義置為final這一步是整個過程的基礎,這同時也是避免并發問題的第一步。
雖說不可變性可以使代碼變得又好又安全,但是由于性能問題,程式員們還是不大願意使用這一特性。其症結在于,為了維護不可變性,我們可能在資料沒發生任何變動的情況下也要進行拷貝操作,而這種無謂的拷貝對性能傷害很大。為了解決這個問題,我們在3.6節中曾經讨論過持久化資料結構以及如何使用這類資料結構來減輕程式在性能方面的負擔。而在持久化資料結構的實作方面,已經有很多現成的第三方庫可供使用,而scala本身也提供了這類資料結構。由于java也有實作好的持久化資料結構可用,是以我們就無需專門為使用這個特性而去換用自己不熟悉的語言。
除了不可變性之外,我們還希望能獲得一些事務運作所需要的資料結構——這些資料結構的值是不可變的,但其實體可以在托管事務中被改變。akka提供了兩種托管資料結構——transactionalvector和transactionalmap。這兩種資料結構源自于高效的scala資料結構,其工作原理和java的list、map類似。下面就讓我們一起來學習如何在java和scala中使用transactionalmap
<b>在java中使用事務集合類</b>
在java中使用transactionalmap是非常簡單的。例如,下面我們一起來寫一個為運動員們記錄得分的程式,其中對于得分的更新操作是并發執行的。這裡我們将不采用同步或鎖的方式,而是把所有更新操作都放在事務中處理。示例代碼如下所示:
<code>01</code>
<code>public</code> <code>class</code> <code>scores {</code>
<code>02</code>
<code> </code><code>final</code> <code>private</code> <code>transactionalmap<string, integer> scorevalues =</code>
<code>03</code>
<code> </code><code>new</code> <code>transactionalmap<string, integer>();</code>
<code>04</code>
<code> </code><code>final</code> <code>private</code> <code>ref<long> updates = </code><code>new</code> <code>ref<long>(0l);</code>
<code>05</code>
<code> </code><code>public</code> <code>void</code> <code>updatescore(</code><code>final</code> <code>string name, </code><code>final</code> <code>int</code> <code>score) {</code>
<code>06</code>
<code> </code><code>new</code> <code>atomic() {</code>
<code>07</code>
<code> </code><code>public</code> <code>object atomically() {</code>
<code>08</code>
<code> </code><code>scorevalues.put(name, score);</code>
<code>09</code>
<code> </code><code>updates.swap(updates.get() + </code><code>1</code><code>);</code>
<code>10</code>
<code> </code><code>if</code> <code>(score == </code><code>13</code><code>)</code>
<code>11</code>
<code> </code><code>throw</code> <code>new</code> <code>runtimeexception(</code><code>"reject this score"</code><code>);</code>
<code>12</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code>13</code>
<code> </code><code>}</code>
<code>14</code>
<code> </code><code>}.execute();</code>
<code>15</code>
<code> </code><code>}</code>
<code>16</code>
<code> </code><code>public</code> <code>iterable<string> getnames() {</code>
<code>17</code>
<code> </code><code>return</code> <code>asjavaiterable(scorevalues.keyset());</code>
<code>18</code>
<code>19</code>
<code> </code><code>public</code> <code>long</code> <code>getnumberofupdates() { </code><code>return</code> <code>updates.get(); }</code>
<code>20</code>
<code> </code><code>public</code> <code>int</code> <code>getscore(</code><code>final</code> <code>string name) {</code>
<code>21</code>
<code> </code><code>return</code> <code>scorevalues.get(name).get();</code>
<code>22</code>
<code>23</code>
<code>}</code>
在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類所實作的這些功能:
<code>package</code> <code>com.agiledeveloper.pcj;</code>
<code>public</code> <code>class</code> <code>usescores {</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(</code><code>final</code> <code>string[] args) {</code>
<code> </code><code>final</code> <code>scores scores = </code><code>new</code> <code>scores();</code>
<code> </code><code>scores.updatescore(</code><code>"joe"</code><code>, </code><code>14</code><code>);</code>
<code> </code><code>scores.updatescore(</code><code>"sally"</code><code>, </code><code>15</code><code>);</code>
<code> </code><code>scores.updatescore(</code><code>"bernie"</code><code>, </code><code>12</code><code>);</code>
<code> </code><code>system.out.println(</code><code>"number of updates: "</code> <code>+ scores.getnumberofupdates());</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>scores.updatescore(</code><code>"bill"</code><code>, </code><code>13</code><code>);</code>
<code> </code><code>} </code><code>catch</code><code>(exception ex) {</code>
<code> </code><code>system.out.println(</code><code>"update failed for score 13"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>for</code><code>(string name : scores.getnames()) {</code>
<code> </code><code>system.out.println(</code>
<code> </code><code>string.format(</code><code>"score for %s is %d"</code><code>, name, scores.getscore(name)));</code>
上例中,我們先是添加了三個正常的運動員成績,随後又增加了一個可以導緻事務復原的成績。但由于事務的存在,是以最後一個成績更新操作最終是無效的。而在代碼的最後,我們會周遊并輸出事務性map裡面的所有資料。下面讓我們觀察一下這段代碼的輸出結果:
<b>在scala中使用事務集合類</b>
在scala中,我們可以用與java類似的方式來使用事務集合類。隻不過由于這次是在scala中,是以這裡我們需要使用scala的内部疊代器而不是javaconversions門面(facade)。下面讓我們把scores類翻譯成scala代碼:
<code>class</code> <code>scores {</code>
<code> </code><code>private</code> <code>val</code> <code>scorevalues </code><code>=</code> <code>new</code> <code>transactionalmap[string, int]()</code>
<code> </code><code>private</code> <code>val</code> <code>updates </code><code>=</code> <code>ref(</code><code>0</code><code>l)</code>
<code> </code><code>def</code> <code>updatescore(name </code><code>:</code> <code>string, score </code><code>:</code> <code>int) </code><code>=</code> <code>{</code>
<code> </code><code>atomic {</code>
<code> </code><code>scorevalues.put(name, score)</code>
<code> </code><code>updates.swap(updates.get() + </code><code>1</code><code>)</code>
<code> </code><code>if</code> <code>(score </code><code>==</code> <code>13</code><code>) </code><code>throw</code> <code>new</code> <code>runtimeexception(</code><code>"reject this score"</code><code>)</code>
<code> </code><code>def</code> <code>foreach(codeblock </code><code>:</code> <code>((string, int)) </code><code>=</code><code>> unit) </code><code>=</code>
<code> </code><code>scorevalues.foreach(codeblock)</code>
<code> </code><code>def</code> <code>getnumberofupdates() </code><code>=</code> <code>updates.get()</code>
如上所示,updatescore()函數與java版本基本是相同的。唯一有點差別的地方是,我們去掉了getnames()函數和getscore()函數,并為foreach()提供了内部疊代器來周遊map中的資料。我們在下面所列出了scala版usescores類的實作,這段代碼是其java版代碼的直譯:
<code>package</code> <code>com.agiledeveloper.pcj</code>
<code>object</code> <code>usescores {</code>
<code> </code><code>def</code> <code>main(args </code><code>:</code> <code>array[string]) </code><code>:</code> <code>unit </code><code>=</code> <code>{</code>
<code> </code><code>val</code> <code>scores </code><code>=</code> <code>new</code> <code>scores()</code>
<code> </code><code>scores.updatescore(</code><code>"joe"</code><code>, </code><code>14</code><code>)</code>
<code> </code><code>scores.updatescore(</code><code>"sally"</code><code>, </code><code>15</code><code>)</code>
<code> </code><code>scores.updatescore(</code><code>"bernie"</code><code>, </code><code>12</code><code>)</code>
<code> </code><code>println(</code><code>"number of updates: "</code> <code>+ scores.getnumberofupdates())</code>
<code> </code><code>scores.updatescore(</code><code>"bill"</code><code>, </code><code>13</code><code>)</code>
<code> </code><code>} </code><code>catch</code> <code>{</code>
<code> </code><code>case</code> <code>ex </code><code>=</code><code>> println(</code><code>"update failed for score 13"</code><code>)</code>
<code> </code><code>scores.foreach { mapentry </code><code>=</code><code>></code>
<code> </code><code>val</code> <code>(name, score) </code><code>=</code> <code>mapentry</code>
<code> </code><code>println(</code><code>"score for "</code> <code>+ name + </code><code>" is "</code> <code>+ score)</code>
不出所料,測試用例的輸出結果也與java版代碼如出一轍: