閱讀581 返回首頁    go 技術社區[雲棲]


軟件事務內存導論(十)處理寫偏斜異常

處理寫偏斜異常

在6.6節中,我們曾經簡單討論了寫偏斜(write skew)以及Clojure STM是如何解決這個問題的。Akka同樣提供了處理寫偏斜問題的支持,但是需要我們配置一下才能生效。OK,一聽到配置這個詞可能讓你覺得有些提心吊 膽,但實際操作起來其實起來還是蠻簡單的。下麵就讓我們首先了解一下Akka在不進行任何配置情況下的默認行為。

讓我們回顧一下之前曾經見到過的那個多個賬戶共享同一個聯合餘額最低限製例子。首先我們創建了一個名為Portfolio的類來保存支票賬戶餘額和 儲蓄賬戶餘額。根據銀行規定,這兩個賬戶的總餘額不得低於$1000。在Portfolio類的代碼中我們用Java重新實現了withdraw()函 數。在該函數中,我們先讀取兩個賬戶的餘額,將二者相加得到總餘額,並在等待一個故意插進去的延時(引入這個延時的目的是為了人為製造事務衝突的環境)之 後,從其中一個賬戶餘額中減掉給定數量的金額(當然,在操作之前需要判斷減掉這個數量後總餘額不少於$1000)。最後需要注意的 是,withdraw()函數是在一個使用了默認設置的事務中完成上述操作的。

01 public  class  Portfolio  {
02     final  private  Ref<Integer>  checkingBalance  =  new  Ref<Integer>(500);
03     final  private  Ref<Integer>  savingsBalance  =  new  Ref<Integer>(600);
04     public  int  getCheckingBalance()  {  return  checkingBalance.get();  }
05     public  int  getSavingsBalance()  {  return  savingsBalance.get();  }
06  
07     public  void  withdraw(final  boolean  fromChecking,  final  int  amount)  {
08         new  Atomic<Object>()  {
09             public  Object  atomically()  {
10                 final  int  totalBalance  =
11                     checkingBalance.get()  +  savingsBalance.get();
12                 try  {  Thread.sleep(1000);  }  catch(InterruptedException  ex)  {}
13                 if(totalBalance  -  amount  >=  1000)  {
14                     if(fromChecking)
15                         checkingBalance.swap(checkingBalance.get()  -  amount);
16                     else
17                         savingsBalance.swap(savingsBalance.get()  -  amount);
18                 }
19                 else
20                     System.out.println(
21                         "Sorry,  can't  withdraw  due  to  constraint  violation");
22                     return  null;
23                 }
24         }.execute();
25     }
26 }

下麵讓我們創建兩個事務來並發地更改賬戶內的餘額:

01 public  class  UsePortfolio  {
02     public  static  void  main(final  String[]  args)  throws  InterruptedException  {
03         final  Portfolio  portfolio  =  new  Portfolio();
04         int  checkingBalance  =  portfolio.getCheckingBalance();
05         int  savingBalance  =  portfolio.getSavingsBalance();
06         System.out.println("Checking  balance  is  "  +  checkingBalance);
07         System.out.println("Savings  balance  is  "  +  savingBalance);
08         System.out.println("Total  balance  is  "  +
09             (checkingBalance  +  savingBalance));
10         final  ExecutorService  service  =  Executors.newFixedThreadPool(10);
11         service.execute(new  Runnable()  {
12             public  void  run()  {  portfolio.withdraw(true100);  }
13         });
14         service.execute(new  Runnable()  {
15             public  void  run()  {  portfolio.withdraw(false100);  }
16         });
17         service.shutdown();
18         Thread.sleep(4000);
19         checkingBalance  =  portfolio.getCheckingBalance();
20         savingBalance  =  portfolio.getSavingsBalance();
21         System.out.println("Checking  balance  is  "  +  checkingBalance);
22         System.out.println("Savings  balance  is  "  +  savingBalance);
23         System.out.println("Total  balance  is  "  +
24         (checkingBalance  +  savingBalance));
25         if(checkingBalance  +  savingBalance  <  1000)
26         System.out.println("Oops,  broke  the  constraint!");
27     }
28 }

正如我們在輸出結果中所看到的那樣,在默認情況下,Akka沒能避免寫偏斜問題,兩個事務違反了銀行的規定,即都從賬戶裏取出了錢。

Checking  balance  is  500
Savings  balance  is  600
Total  balance  is  1100
Checking  balance  is  400
Savings  balance  is  500
Total  balance  is  900
Oops,  broke  the  constraint!

現在到了該徹底解決這個問題的時候了。讓我們祭出TransactionFactory這個能幫助我們在程序裏對事物進行配置的法寶,在Portfolio類的第9行插入下麵這段創建工廠實例的代碼:

1 akka.stm.TransactionFactory  factory  =
2     new  akka.stm.TransactionFactoryBuilder()
3         .setWriteSkew(false)
4         .setTrackReads(true)
5         .build();

在插進來的這幾行代碼中,我們創建了一個TransactionFactoryBuilder,並將writeSkew和trackReads屬性 分別設置為false和true。與Clojure STM對於ensure的處理類似,這兩個設置項的目的是告訴事務要在其運行過程中對讀操作進行追蹤,同時也會使事務在讀數據的過程中對賬戶餘額變量加讀 鎖直至提交開始為止。

除了上麵提到的幾處更改之外,Portfolio和UsePortfolio的其他代碼都保持不變。而在對事務進行了上述設置之後,其輸出結果如下所示:

Checking  balance  is  500
Savings  balance  is  600
Total  balance  is  1100
Sorry,  can't  withdraw  due  to  constraint  violation
Checking  balance  is  400
Savings  balance  is  600
Total  balance  is  1000

由於並發執行的不可預測性,我們不能確定兩個事務到底哪個會勝出。但是我們可以從輸出結果中看到,在所有操作結束後兩個賬戶的餘額是不同的,而在6.6節的Clojure示例中,最終兩個賬戶餘額是相同的。我們可以通過多次運行這兩個實例來觀察二者之間的差異。

在本節我們是用Java完成整個示例的。如果換成Scala,則我們可以使用在6.10節中學習的語法來配置事務的writeSkew和trackReads屬性。


文章轉自 並發編程網-ifeve.com

最後更新:2017-05-22 16:01:55

  上一篇:go  Java內存模型
  下一篇:go  戲(細)說Executor框架線程池任務執行全過程(下)