论坛首页 Java企业应用论坛

疑惑:有没有人真正用多线程工具(比如groboutils)测试过Spring的事务处理?

浏览 28072 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-08-04  
mysaga 写道
mikewang 写道
看了这么多回帖, 我只想说一个问题

Mysql数据库真的支持事务吗?

看看mysql 文档就可以找到答案, 对于MyISAM 表, 是不支持事务的, InnoDB 是对事务有限支持。 支持的方式就是用锁。

了解了这点, 就不难理解测试的结果了!


哦,我不太侧重数据库方面。但我认为,InnoDB 的所谓“有限支持”,还是能应对我在顶楼提出的基本要求的。

以后有机会换个oracle之类的试试。


顶楼的实验结果没错呀啊, MYISAM 表没有锁, 但数据不一致, InnoDB 数据一致, 但有锁。 这就足够说明问题了。

顶楼做了正确的实验, 但得出的结论并不完备。 MySql InnoDB 的事务是基于记录锁来实现的, 并且实现的效率并不高而已。

0 请登录后投票
   发表时间:2009-08-04  
mysaga 写道
andyyehoo 写道
其实再仔细看看这个设计的代码,还是很有问题,不能满足这样的场景,500个线程,同时不停的从不同的帐户里面,向一个账户存款和取款,这个场景下,数据库报错的信息简直就会如雪花。
......


无所谓。只要系统能(1),正确的提示那些操作失败的用户“你的操作失败,请稍后再试”,并且(2),绝对保证数据的一致性:失败的操作没有任何效果,成功的操作能够被持久,银行没有损失,用户也没有损失——那么,一个健壮系统最基本的必要条件就满足了。

现在我的测试用例已经证明了这一点,所以我认为它是成功的。

当然,接下来的工作还很多。


关于缓存方面,太久没用Hibernate,已经不确定默认的行为了,所以我说不确定因素。但是实际上,重点不是到底默认行为如何,不是重点,有这样的可能性就是危险。系统的关键代码,强健性本来就应该是重中之重,你的代码,只要是万一打开了Hibernate的二级缓存,就是不安全的,这样的设计,对于银行系统的来说,就是不够的。你换个角度,不是这段代码的编写者,而是作为公司的Code Reviewer,在单独看到这样的代码的时候,肯定会有这样的第一直觉浮现。

我也明白对于你来说,你的测试目的达到了。但是作为一个好的架构师,考虑问题的深度不能只满足于这样浅的目标,把问题解决了就了事,要考虑更优和更合适的方案。批量转帐是网上支付系统的一个典型业务,想想支付宝之类的业务量,这样的代码,是否能够胜任?Hibernate的UVersion,在实际应用的性能,还是很值得磋商的,而且实际上,不是用于这种场合的。

当然,正如你所说的,你想证明的只是测试用例的成功,而不是代码的成功,接下来的工作,还很多
0 请登录后投票
   发表时间:2009-08-04  
mysaga 写道
mikewang 写道
看了这么多回帖, 我只想说一个问题

Mysql数据库真的支持事务吗?

看看mysql 文档就可以找到答案, 对于MyISAM 表, 是不支持事务的, InnoDB 是对事务有限支持。 支持的方式就是用锁。

了解了这点, 就不难理解测试的结果了!


哦,我不太侧重数据库方面。但我认为,InnoDB 的所谓“有限支持”,还是能应对我在顶楼提出的基本要求的。

以后有机会换个oracle之类的试试。


InnoDB支持事务的四个隔离级别,比Oracle默认驱动的2个还完整,只不过效率方面就需要考究了
0 请登录后投票
   发表时间:2009-08-04   最后修改:2009-08-04
引用
1.关于hibernate的二级缓存构造出来的对象是线程不安全的

这种说法是不对的,对象不是从缓存直接取出来的,缓存的是分解了的对象,每次取,对象是被重新构造出来的。
至于数据与数据库数据库一致性方面:因为二级缓存本身提供了几种策略,有不一致的也有一致的

引用
2.锁问题

悲观锁或是乐观锁在例子中高并发的情况下,死锁都是必然的,因为都要执行数据库更新,无可避免的产生行锁,
这个时候就依赖于数据库的死锁检查机制了,悲观锁从读就产生,所以死锁的几率高些;乐观锁不是读的时候就上锁,支持长事务,泡杯茶后继续那种。
至于使用java的锁机制:简单点,同步所有对账户的操作,并发效果差些,无死锁。
使用高级点的锁机制,一开始就锁定所有lock1.tryLock(),lock2.tryLock(),lock...,lockn.tryLock(),依然无死锁。
11 请登录后投票
   发表时间:2009-08-04   最后修改:2009-08-04
rain2005 写道
即使只是使用 servlet + jdbc,也不用手写SQL代码来管理什么事务了——因为你根本不需要事务嘛。

这就没有什么可以说的了,第一次听说servlet + jdbc根本不需要事务。建议你先搞清楚什么是事务。。。


少在那边断章取义了。你怎么不引用我的全文??

就因为你先在那边嚷嚷什么:
rain2005 写道
......楼主完全可以这样
线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,这样才是真正的测试spring并发的事务正确性
......


我才说:
mysaga 写道
“线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,” —— 嘿嘿,如果只考虑这种井水不犯河水的操作,还要spring事务处理干嘛?普通的 servlet + jdbc 就搞定了....

所以,俺的测试用例目标还是很明确的:多线程粗暴蹂躏共享的数据库记录,看你 spring + hibernate 如何反应...


然后你说:
rain2005 写道
明白你的意思了。
spring + hibernate对待数据库记录的方式和servlet + jdbc没有什么区别吗?要保证事务一致性那是数据库的职责把。不就是更新锁和乐观锁?这好像不关spring + hibernate什么事把?


接下来才是我的那段话,注意看红色的全文:
mysaga 写道

哈哈,俺的意思是说,有了spring + hibernate,你自己就不用手写SQL代码来管理事务了——不是你不需要事务管理,而是 spring + hibernate 帮你搞定绝大多数东东。

而假如只考虑“线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,”这种井水不犯河水的操作,就不用担心任何事务隔离级别、锁之类的东西。因此,即使只是使用 servlet + jdbc,不用手写SQL代码来管理什么事务了——因为你根本不需要事务嘛。


我是在指出你最早的谬误,而且我的本意是“servlet + jdbc”同样需要(写在SQL里面的)事务管理,在你所假想的场景下不需要,也因此,你提出的这个场景根本测不了什么“spring并发的事务正确性”——只不过,我的话比较委婉罢了。

你却从我的整段话里截了后一半,把我的意思完全反了过来。这算什么??
1 请登录后投票
   发表时间:2009-08-04   最后修改:2009-08-04
andyyehoo 写道


关于缓存方面,太久没用Hibernate,已经不确定默认的行为了,所以我说不确定因素。但是实际上,重点不是到底默认行为如何,不是重点,有这样的可能性就是危险。系统的关键代码,强健性本来就应该是重中之重,你的代码,只要是万一打开了Hibernate的二级缓存,就是不安全的,这样的设计,对于银行系统的来说,就是不够的。你换个角度,不是这段代码的编写者,而是作为公司的Code Reviewer,在单独看到这样的代码的时候,肯定会有这样的第一直觉浮现。



嘿嘿,这就是写测试用例的目的之一啊!

假如某天某人在我不知情的情况下打开了二级缓存,出现了问题,我再重新跑一遍测试,就发现了嘛!

	public void testMultiThreadTransfer() throws Throwable {

		// 验证在仅有一级缓存的情况下,用同样的主键交给 accountService.findById,它每次
		// 返回的是相等,但并不同一的实例。
		// 当然了,Account 对象的 equals 必须被正确的覆盖先。
		for (int i = 0; i < accountIds.length; i++) {
			assertEquals(accountService.findById(accountIds[i]),
					accountService.findById(accountIds[i]));
			assertNotSame(accountService.findById(accountIds[i]),
					accountService.findById(accountIds[i]));
		}
	}


这里的 assertNotSame 就会失败。

所以我说,在最基本的方面,我的用例到达了目的。

当然,你说的也是我的意思,接下来要做的还很多。
0 请登录后投票
   发表时间:2009-08-04   最后修改:2009-08-04
mysaga 写道
rain2005 写道
即使只是使用 servlet + jdbc,也不用手写SQL代码来管理什么事务了——因为你根本不需要事务嘛。

这就没有什么可以说的了,第一次听说servlet + jdbc根本不需要事务。建议你先搞清楚什么是事务。。。


少在那边断章取义了。你怎么不引用我的全文??

就因为你先在那边嚷嚷什么:
rain2005 写道
......楼主完全可以这样
线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,这样才是真正的测试spring并发的事务正确性
......


我才说:
mysaga 写道
“线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,” —— 嘿嘿,如果只考虑这种井水不犯河水的操作,还要spring事务处理干嘛?普通的 servlet + jdbc 就搞定了....

所以,俺的测试用例目标还是很明确的:多线程粗暴蹂躏共享的数据库记录,看你 spring + hibernate 如何反应...


然后你说:
rain2005 写道
明白你的意思了。
spring + hibernate对待数据库记录的方式和servlet + jdbc没有什么区别吗?要保证事务一致性那是数据库的职责把。不就是更新锁和乐观锁?这好像不关spring + hibernate什么事把?


接下来才是我的那段话,注意看红色的全文:
mysaga 写道

哈哈,俺的意思是说,有了spring + hibernate,你自己就不用手写SQL代码来管理事务了——不是你不需要事务管理,而是 spring + hibernate 帮你搞定绝大多数东东。

而假如只考虑“线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,”这种井水不犯河水的操作,就不用担心任何事务隔离级别、锁之类的东西。因此,即使只是使用 servlet + jdbc,不用手写SQL代码来管理什么事务了——因为你根本不需要事务嘛。


我是在指出你最早的谬误,而且我的本意是“servlet + jdbc”
同样需要(写在SQL里面的)事务管理,
在你所假想的场景下不需要,也因此,你提出的这个场景
根本测不了什么“spring并发的事务正确性”——只不过,我的话比较委婉罢了。

你却从我的整段话里截了后一半,把我的意思完全反了过来。这算什么??


按照你的说法,线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F这个是不需要事务的???
spring并发的事务正确性按照你的理解就是死锁有提示?拜托,这好像跟spring没有什么关系把,我看你的标题应该是测试数据库的并发正确性。
0 请登录后投票
   发表时间:2009-08-04   最后修改:2009-08-04
rain2005 写道
按照你的说法,线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F这个是不需要事务的???


你又来了。。。。任意曲解、发挥别人的话。。。

我只是说,你的理想场景测试不了“并发的事务控制”,因为三个转帐互不干扰!!

A给B转300块,C给D转500块,E给F转600块,这三个操作同时发生跟先后发生,有什么区别?

在我的测试用例里面如果改成这样,那我告诉你,spring context xml 里面加不加事务处理配置,junit 的测试结果都是绿色条!你还真以为我没试过???

rain2005 写道

spring并发的事务正确性按照你的理解就是死锁有提示?拜托,这好像跟spring没有什么关系把,我看你的标题应该是测试数据库的并发正确性。


又一次断章取义,被抓了现行。

我的原话:

mysaga 写道

无所谓。只要系统能(1),正确的提示那些操作失败的用户“你的操作失败,请稍后再试”,并且(2),绝对保证数据的一致性:失败的操作没有任何效果,成功的操作能够被持久,银行没有损失,用户也没有损失——那么,一个健壮系统最基本的必要条件就满足了。

现在我的测试用例已经证明了这一点,所以我认为它是成功的。


这怎么又跟 spring 没有关系了??难道这第(2)点不是由 spring 的声明式事务处理 + hibernate 帮忙的锁机制实现的吗?

对你,我无话可说。
1 请登录后投票
   发表时间:2009-08-04  
nihongye 写道
引用
1.关于hibernate的二级缓存构造出来的对象是线程不安全的

这种说法是不对的,对象不是从缓存直接取出来的,缓存的是分解了的对象,每次取,对象是被重新构造出来的。
至于数据与数据库数据库一致性方面:因为二级缓存本身提供了几种策略,有不一致的也有一致的

引用
2.锁问题

悲观锁或是乐观锁在例子中高并发的情况下,死锁都是必然的,因为都要执行数据库更新,无可避免的产生行锁,
这个时候就依赖于数据库的死锁检查机制了,悲观锁从读就产生,所以死锁的几率高些;乐观锁不是读的时候就上锁,支持长事务,泡杯茶后继续那种。
至于使用java的锁机制:简单点,同步所有对账户的操作,并发效果差些,无死锁。
使用高级点的锁机制,一开始就锁定所有lock1.tryLock(),lock2.tryLock(),lock...,lockn.tryLock(),依然无死锁。


哦,受教了。
0 请登录后投票
   发表时间:2009-08-05   最后修改:2009-08-05
rain2005 写道
楼主的代码是没有问题的,其实我想表达的意思就是楼主的测试并发大时必然死锁,从楼主的标题看是想测试spring事务的并发,楼主完全可以这样
线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,这样才是真正的测试spring并发的事务正确性

如果想测试程序的健壮性,如死锁可以再写测试用例。

总之保证,每个测试用例目标明确。


好了!花了点时间,完善了我对上述rain2005 所臆想场景的模拟,并证明了此提议的荒谬。

我先给原本的测试类做了些必要的修改,然后为其添加子类 AccountTransferMultiThreadTestAccountsNotConflict。

目的:让每一个转帐线程所选取的转出(from)与转入(to)户头与其它线程相冲突。也就是说,模拟了场景:“楼主完全可以这样 线程1操作帐户A,B,线程2操作帐户C,D,线程3操作帐户E,F,这样才是真正的测试spring并发的事务正确性。”,并证明其提议之荒谬:在这种“井水不犯河水”的操作下,根本测不了什么“spring并发的事务正确性”。

结果:在打开 spring 声明式事务处理的情况下,父测试类 AccountTransferMultiThreadTest(各转帐线程选取的户头出现冲突)与本测试类 AccountTransferMultiThreadTestAccountsNotConflict(各转帐线程选取的户头没有任何冲突)均顺利通过,junit 显示绿色条;

在关闭 spring 声明式事务处理的情况下,父测试类 AccountTransferMultiThreadTest(各转帐线程选取的户头出现冲突)失败,junit 显示红色条。从错误信息发现,转帐前后,所有账户总额不一致;

而在同样关闭 spring 声明式事务处理的情况下,子测试类 AccountTransferMultiThreadTestAccountsNotConflict(各转帐线程选取的户头没有任何冲突)仍然顺利通过,junit 显示绿色条。从打印信息发现,转帐前后,所有账户总额保持一致,并且每个账户的最终余额和记录(balanceTracking)中完全相同;

证明:AccountTransferMultiThreadTestAccountsNotConflict 所模拟的这种(户头没有冲突的)操作完全测试不了“spring并发的事务正确性”,rain2005 的提案没有任何意义;而我在顶楼(当时有小错误,后来已修正)所提出的做法才能真正达到这个目的。

希望大家在发贴的时候要谨慎些。不管你自己有多菜,总有比你更菜的人。你那些不负责任的论断,极有可能给他们造成误导。



附代码:



子测试类:AccountTransferMultiThreadTestAccountsNotConflict.java:
//import 省略

/**
 *
 * 测试类 AccountTransferMultiThreadTestAccountsNotConflict 继承了
 * 测试类 AccountTransferMultiThreadTest。
 *
 * 目的:让每一个转帐线程所选取的转出(from)与转入(to)户头不与其它线程相冲突。也就是说,
 *      模拟了 iteye.com 中某某人所臆想的场景:<b>“楼主完全可以这样 线程1操作帐户A,B,
 *      线程2操作帐户C,D,线程3操作帐户E,F,这样才是真正的测试spring并发的事务正确性。”</b>,
 *      并证明他的提议之荒谬:在这种“井水不犯河水”的操作下,根本测不了什么“spring并发的事务正确性”。
 */
public class AccountTransferMultiThreadTestAccountsNotConflict extends
		AccountTransferMultiThreadTest {

	private LinkedList<Long> accountIdsNotChosen;

	public AccountTransferMultiThreadTestAccountsNotConflict() {
		super();

		// 重新设置父类中定义的 测试户头的总数 和 测试线程总数。
		numOfAccounts = 200; // 测试户头的总数。这里,它必须是偶数。
		numOfTransfers = 100; // 测试线程总数(即转帐总次数。这里,它必须等于 测试户头总数 的一半。)
	}

	protected void setUp() throws Exception {
		super.setUp();

		// 利用“accountIdsNotChosen”,避免重复选取户头。
		accountIdsNotChosen  = new LinkedList<Long>();
		for (Long id : accountIds) {
			accountIdsNotChosen.add(id);
		}
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

	/* (non-Javadoc)
	 * @see xiao.test.spring.testCases.AccountTransferMultiThreadTest#generateTransferThread()
	 */
	@Override
	protected TransferThread generateTransferThread() {
		return new TransferThreadAccountsNotConflict(accountService, accountIds,
				accountIdsNotChosen, balanceTracking);
	}

	/* (non-Javadoc)
	 * @see xiao.test.spring.testCases.AccountTransferMultiThreadTest#testMultiThreadTransfer()
	 */
	@Override
	public void testMultiThreadTransfer() throws Throwable {
		super.testMultiThreadTransfer();
	}

	private static class TransferThreadAccountsNotConflict extends TransferThread {
		private LinkedList<Long> accountIdsNotChosen;

		public TransferThreadAccountsNotConflict(AccountService accountService,
				long[] accountIds, LinkedList<Long> accountIdsNotChosen, Map<Long, BigDecimal> balanceTracking) {

			super(accountService, accountIds, balanceTracking);

			this.accountIdsNotChosen = accountIdsNotChosen;
			if (accountIdsNotChosen.size() <= 1) {
				throw new AppException("There are at most 1 account in 'not chosen list', cannot"
						+ " choose 2 accounts to make a transfer!");
			}
		}

		/* (non-Javadoc)
		 * @see xiao.test.spring.testCases.AccountTransferMultiThreadTest.TransferThread#generateTransferOptions()
		 */
		@Override
		protected void generateTransferOptions() {
			Random randomGenerator = new Random();

			synchronized (accountIdsNotChosen) {
				// 随机选取转出户头
				int i = randomGenerator.nextInt(accountIdsNotChosen.size());
				fromId = accountIdsNotChosen.remove(i);

				// 随机选取转入户头
				i = randomGenerator.nextInt(accountIdsNotChosen.size());
				toId = accountIdsNotChosen.remove(i);
			}

			// 随机选取转帐数额(0 ~ 149元之间)
			amount = BigDecimal.valueOf(randomGenerator.nextInt(150));
		}

		/* (non-Javadoc)
		 * @see xiao.test.spring.testCases.AccountTransferMultiThreadTest.TransferThread#runTest()
		 */
		@Override
		public void runTest() throws Throwable {
			super.runTest();
		}

	}
}



原测试类 AccountTransferMultiThreadTest.java,已做必要修改:
//import 省略

/**
 * 测试类 AccountTransferMultiThreadTest,使用了 groboutils 以实现多线程测试。
 *
 * 每个测试线程从一定数量的测试户头中随机选取一对 转出/转入 户头,然后进行一次随机数额的转帐。
 *
 * 测试户头总数由常量 numOfAccounts 设定。
 *
 * 测试线程总数由常量 numOfTransfers 设定。
 *
 */
public class AccountTransferMultiThreadTest extends TestCase {
	// 每个测试户头的初始余额为1000元
	private static final BigDecimal INIT_BALANCE = BigDecimal.valueOf(100000L, 2);
	private static int successTransfers = 0;

	protected int numOfAccounts; // 测试户头的总数
	protected int numOfTransfers; // 测试线程总数(即转帐总次数)
	private ApplicationContext context;
	protected AccountService accountService;
	protected long[] accountIds;
	protected Map<Long, BigDecimal> balanceTracking = new HashMap<Long, BigDecimal>();;

	public AccountTransferMultiThreadTest() {
		super();
		numOfAccounts = 10; // 测试户头的总数
		numOfTransfers = 300; // 测试线程总数(即转帐总次数)

		context = new ClassPathXmlApplicationContext("xiao/test/spring/*Context.xml");
		accountService = (AccountService) context.getBean("accountService");
	}

	/* (non-Javadoc)
	 * @see junit.framework.TestCase#setUp()
	 *
	 * 在setUp方法中,生成测试所需的Spring Application Context, 并在数据库中创建
	 * 一定数量的户头(Account),供多线程测试使用。
	 *
	 */
	protected void setUp() throws Exception {
		super.setUp();

		Account[] accounts = new Account[numOfAccounts];
		accountIds = new long[accounts.length];
		for (int i = 0; i < accounts.length; i++) {
			accounts[i] = new Account();
			accounts[i].setBalance(INIT_BALANCE);

			// 将当前生成的户头写入数据库
			accountService.create(accounts[i]);

			// 重要步骤!将当前生成的户头主键记录下来,以供测试线程使用
			accountIds[i] = (Long)accounts[i].getId();
		}
	}

	/* (non-Javadoc)
	 * @see junit.framework.TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		super.tearDown();
	}

	protected Account[] getAccounts() {
		Account[] accounts = new Account[accountIds.length];
		for (int i = 0; i < accountIds.length; i++) {
			// 从数据库获取这个户头对象
			accounts[i] = accountService.findById(accountIds[i]);
		}
		// 返回户头数组
		return accounts;
	}

	protected TransferThread generateTransferThread() {
		return new TransferThread(accountService, accountIds, balanceTracking);
	}

	public void testMultiThreadTransfer() throws Throwable {

		// 验证在仅有一级缓存的情况下,用同样的主键交给 accountService.findById,它每次
		// 返回的是相等,但并不同一的实例。
		// 当然了,Account 对象的 equals 必须被正确的覆盖先。
		for (int i = 0; i < accountIds.length; i++) {
			assertEquals(accountService.findById(accountIds[i]),
					accountService.findById(accountIds[i]));
			assertNotSame(accountService.findById(accountIds[i]),
					accountService.findById(accountIds[i]));
		}

		// 获取户头对象数组
		Account[] accounts = getAccounts();
		//System.out.printf("Starting %s transfers...\n", numOfTransfers);

		// 记录测试前的所有户头总余额
		BigDecimal total1 = accountService.getTotalBalance(accounts);

		// 记录测试前的所有户头的余额
		for (Account account : accounts) {
			balanceTracking.put(account.getId(), account.getBalance());
		}

		// 生成所有测试线程
		TestRunnable[] tr = new TestRunnable[numOfTransfers];
		long start = System.currentTimeMillis();
		for (int i = 0; i < tr.length; i++) {
			tr[i] = generateTransferThread();
		}

		// 生成测试线程运行器
		MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(tr);

		// 运行测试线程
		mttr.runTestRunnables();
		long used = System.currentTimeMillis() - start;
		System.out.printf("Total: %s transfers used %s milli-seconds.\n", numOfTransfers, used);

		// 获取测试后所有户头总余额
		Account[] accounts2 = getAccounts();
		BigDecimal total2 = accountService.getTotalBalance(accounts2);

		// 确认测试前后,所有户头总余额还是一致的。
		assertEquals(total1, total2);

		// 确认测试前后,所有户头余额与转帐记录相一致。
		System.out.printf("Successful transfers: %s\n", successTransfers);
		System.out.println(balanceTracking);
		for (Account account : accounts2) {
			assertEquals(balanceTracking.get(account.getId()), account.getBalance());
		}
	}

	/*
	 * 测试线程类定义
	 */
	protected static class TransferThread extends TestRunnable {
		private AccountService accountService;
		private Map<Long, BigDecimal> balanceTracking;
		protected long[] accountIds;
		protected long fromId;
		protected long toId;
		protected BigDecimal amount;

		public TransferThread(AccountService accountService,
				long[] accountIds, Map<Long, BigDecimal> balanceTracking) {
			super();
			this.accountService = accountService;
			this.accountIds = accountIds;
			this.balanceTracking = balanceTracking;
		}

		protected void generateTransferOptions() {
			Random randomGenerator = new Random();

			// 随机选取转出户头
			fromId = accountIds[
			                      randomGenerator.nextInt(accountIds.length)
			                      ];

			// 随机选取转入户头
			toId = accountIds[
			                     randomGenerator.nextInt(accountIds.length)
			                     ];

			// 确保转出、转入户头不是同一个
			while (toId == fromId) {
				toId = accountIds[
				                randomGenerator.nextInt(accountIds.length)
				                ];
			}

			// 随机选取转帐数额(0 ~ 149元之间)
			amount = BigDecimal.valueOf(randomGenerator.nextInt(150));
		}

		@Override
		public void runTest() throws Throwable {
			generateTransferOptions();

			boolean success;
			// 转帐!
			try {
				accountService.transfer(
						accountService.findById(toId),
						accountService.findById(fromId),
						amount);
				success = true;
			} catch (AppException ae) {
				// 捕捉运行时间异常“AppException”。在真实的系统中,这里必须通知用户:转帐失败,请稍后再试。
				//System.out.println("AppException:" + ae.getMessage());
				success = false;
			} catch (Throwable t) {
				// 捕捉所有异常。在真实的系统中,这里必须通知用户:转帐失败,请稍后再试。
				success = false;
			}
			if (success) {
				// 以下记录每一次成功的转帐后,被影响户头的余额。假如在 accountService.transfer 中有异常抛出,
				// 这一记录动作将不会执行。
				synchronized (balanceTracking) {
					successTransfers ++;
					BigDecimal oriFromBal = balanceTracking.get(fromId);
					BigDecimal oriToBal = balanceTracking.get(toId);
					System.out.printf("Successful transfer no.%s: account[%s] (bal: %s) -> account[%s] (bal: %s),"
							+ " amount (%s)\n", successTransfers, fromId, oriFromBal, toId, oriToBal, amount);
					balanceTracking.put(fromId, oriFromBal.subtract(amount));
					balanceTracking.put(toId, oriToBal.add(amount));
				}
			}
		}
	}
}

1 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics