1. 程式人生 > >軟體事務記憶體導論(八)提交和回滾事件

軟體事務記憶體導論(八)提交和回滾事件

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

提交和回滾事件

Java的try-catch-finally語法結構不但使我們可以安全地處理異常,還能夠在程式丟擲異常時選擇性地執行一些程式碼。同樣地,我們也可以控制程式在事務成功提交之後去執行某段程式碼,而當事務回滾時則去執行另一段程式碼。StmUtils中的deferred()和compensatiing()這兩個函式分別提供了上述功能。特別地,在實現事務的過程中,為保證事務能順利完成,我們通常會加入一些帶副作用的邏輯,而deferred()函式則是一個執行所有這部分邏輯的絕佳地點。


Java中的提交和回滾事件

我們可以把想要在事務成功完成之後執行的程式碼放在Runnable介面實現部分的程式碼塊中,並將其作為引數傳給StmUtils的deferred()函式。同樣地,我們也可以把想要在事務失敗之後執行的程式碼封裝在Runnable介面中傳給compensating()函式。由於這兩個函式必須在事務的環境下執行,所以我們只有在automically()函式的函式體中才能呼叫他們。

public  class  Counter  {
	private  final  Ref<Integer>  value  =  new  Ref<Integer>(1);
	public  void  decrement()  {
		new  Atomic<Integer>()  {
			public  Integer  atomically()  {
				deferred(new  Runnable()  {
					public  void  run()  {
						System.out.println(
							"Transaction  completed...send  email,  log,  etc.");
					}
				});
			compensating(new  Runnable()  {
				public  void  run()  {
					System.out.println("Transaction  aborted...hold  the  phone");
				}
			});
			if(value.get()  <=  0)
				throw  new  RuntimeException("Operation  not  allowed");
			value.swap(value.get()  -  1);
			return  value.get();
			}
		}.execute();
	}
}

在Counter類的定義程式碼中我們看到,Counter類僅含有一個名為decrement()的例項方法。在這個方法中,我們繼承了Atomic類並實現了atomically()函式。在前面的例子中,我們都僅僅是簡單地把事務的邏輯程式碼放在這個位置。而現在,除了原有的邏輯程式碼之外,我們把事務成功和事務回滾之後要執行的程式碼也放到了atomically()裡面。下面讓我們構建一個簡單的測試用例來驗證一下Counter的功能:

package  com.agiledeveloper.pcj;
public  class  UseCounter  {
	public  static  void  main(final  String[]  args)  {
		Counter  counter  =  new  Counter();
		counter.decrement();
		System.out.println("Let's  try  again...");
		try  {
			counter.decrement();
		}  catch(Exception  ex)  {
			System.out.println(ex.getMessage());
		}
	}
}

通過執行UseCounter,我們可以清楚地觀察到事務成功完成和失敗時程式的執行邏輯:

Transaction  aborted...hold  the  phone
Transaction  completed...send  email,  log,  etc.
Let's  try  again...
Transaction  aborted...hold  the  phone
Operation  not  allowed

當第一次呼叫decrement()函式併成功完成事務之後,封裝在deferred()函式內的程式碼邏輯將被執行。而當我們第二次呼叫decrement()時,由於事務執行過程中丟擲了異常,所以事務將被回滾,而封裝在compensating()函式內的程式碼也將被執行。最後我們需要注意的是,輸出結果中最頂部的那個非預期的重試是由我們之前在6.9節中曾討論過的預設優化設定所導致的。

deferred()函式是一個執行事務收尾工作以便使其效果固化的絕佳地點,所以我們可以在裡面隨便進行列印、顯示訊息、釋出通知以及提交資料庫事務等操作。如果我們在事務之外有什麼遺留的工作待完成,那麼這個函式無疑是最好的完成地點。與deferred()類似的是,compensating()函式是記錄事務失敗資訊的好地方。此外,如果我們之前已經將非託管物件(即那些沒有使用Akka Ref進行管理的物件)與託管物件混雜在一起的話,那麼這裡也是糾正這一錯誤的合適地點——但是由於這種做法太容易出錯,所以請你最好避免採用這樣的設計思路。

Scala中的提交和回滾事件

在Scala中,我們處理提交和回滾事件的方式與Java基本相同,唯一區別就是在Scala中我們可以將閉包/函式值直接傳遞給deferred()和compensating()。下面讓我們將Counter類由Java轉譯成Scala。

class  Counter  {
	private  val  value  =  Ref(1)
	def  decrement()  =  {
		atomic  {
			deferred  {  println("Transaction  completed...send  email,  log,  etc.")  }
			compensating  {  println("Transaction  aborted...hold  the  phone")  }
			if(value.get()  <=  0)
				throw  new  RuntimeException("Operation  not  allowed")
			value.swap(value.get()  -  1)
			value.get()
		}
	}
}

在上面的程式碼中,我們將事務執行成功時所要執行的那部分程式碼封裝在一個閉包中,然後將其作為引數傳遞給deferred()函式。類似地,事務回滾時所要執行的程式碼也被作為一個閉包賦給了compensating()函式。與此同時,這兩個函式又與事務邏輯程式碼一起被置於表示atomic()函式的閉包當中。這段程式碼再次彰顯了Scala在語法上簡潔明瞭的特徵。下面讓我們將UseConuter類也從Java轉譯成Scala:

package  com.agiledeveloper.pcj
object  UseCounter  {
	def  main(args  :  Array[String])  :  Unit  =  {
		val  counter  =  new  Counter()
		counter.decrement()
		println("Let's  try  again...")
		try  {
			counter.decrement()
		}  catch  {
			case  ex  =>  println(ex.getMessage())
		}
	}
}

如下所示,Scala版程式碼的執行結果與Java版的結果是完全相同的:

Transaction  aborted...hold  the  phone
Transaction  completed...send  email,  log,  etc.
Let's  try  again...
Transaction  aborted...hold  the  phone
Operation  not  allowed


丁 一

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