1. 程式人生 > >軟體事務記憶體導論(十)處理寫偏斜異常

軟體事務記憶體導論(十)處理寫偏斜異常

宣告:本文是《Java虛擬機器併發程式設計》的第六章,感謝華章出版社授權併發程式設計網站釋出此文,禁止以任何形式轉載此文。

處理寫偏斜異常

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

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

public  class  Portfolio  {
	final  private  Ref<Integer>  checkingBalance  =  new  Ref<Integer>(500);
	final  private  Ref<Integer>  savingsBalance  =  new  Ref<Integer>(600);
	public  int  getCheckingBalance()  {  return  checkingBalance.get();  }
	public  int  getSavingsBalance()  {  return  savingsBalance.get();  }

	public  void  withdraw(final  boolean  fromChecking,  final  int  amount)  {
		new  Atomic<Object>()  {
			public  Object  atomically()  {
				final  int  totalBalance  =
					checkingBalance.get()  +  savingsBalance.get();
				try  {  Thread.sleep(1000);  }  catch(InterruptedException  ex)  {}
				if(totalBalance  -  amount  >=  1000)  {
					if(fromChecking)
						checkingBalance.swap(checkingBalance.get()  -  amount);
					else
						savingsBalance.swap(savingsBalance.get()  -  amount);
				}
				else
					System.out.println(
						"Sorry,  can't  withdraw  due  to  constraint  violation");
					return  null;
				}
		}.execute();
	}
}

下面讓我們建立兩個事務來併發地更改賬戶內的餘額:

public  class  UsePortfolio  {
	public  static  void  main(final  String[]  args)  throws  InterruptedException  {
		final  Portfolio  portfolio  =  new  Portfolio();
		int  checkingBalance  =  portfolio.getCheckingBalance();
		int  savingBalance  =  portfolio.getSavingsBalance();
		System.out.println("Checking  balance  is  "  +  checkingBalance);
		System.out.println("Savings  balance  is  "  +  savingBalance);
		System.out.println("Total  balance  is  "  +
			(checkingBalance  +  savingBalance));
		final  ExecutorService  service  =  Executors.newFixedThreadPool(10);
		service.execute(new  Runnable()  {
			public  void  run()  {  portfolio.withdraw(true,  100);  }
		});
		service.execute(new  Runnable()  {
			public  void  run()  {  portfolio.withdraw(false,  100);  }
		});
		service.shutdown();
		Thread.sleep(4000);
		checkingBalance  =  portfolio.getCheckingBalance();
		savingBalance  =  portfolio.getSavingsBalance();
		System.out.println("Checking  balance  is  "  +  checkingBalance);
		System.out.println("Savings  balance  is  "  +  savingBalance);
		System.out.println("Total  balance  is  "  +
		(checkingBalance  +  savingBalance));
		if(checkingBalance  +  savingBalance  <  1000)
		System.out.println("Oops,  broke  the  constraint!");
	}
}

正如我們在輸出結果中所看到的那樣,在預設情況下,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行插入下面這段建立工廠例項的程式碼:

akka.stm.TransactionFactory  factory  =
	new  akka.stm.TransactionFactoryBuilder()
		.setWriteSkew(false)
		.setTrackReads(true)
		.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屬性。


丁 一

英文名ticmy,本站的翻譯主編,主要負責譯文的翻譯和校對工作,目前就職於阿里巴巴,對併發程式設計、JVM執行原理、正則等頗有興趣;個人部落格:http://www.ticmy.com/;同時,還是戶外攝影愛好者。