这几天在练习针对一个账号进行存钱、取钱操作的并发控制。
entiy:Account
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; import java.io.Serializable; @Entity(name = "Account") public class Account implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "AccountNumber") private String accountNumber; /* @Version @Column(name = "Version") private long version;*/ @Column(name = "Status") private String status; @Column(name = "Score") private Double score; @Column(name = "Cas") private Long cas; public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Double getScore() { return score; } public void setScore(Double score) { this.score = score; } public Long getCas() { return cas; } public void setCas(Long cas) { this.cas = cas; } }
dao:AccountDao
import org.springframework.stereotype.Repository; import javax.inject.Inject; import javax.persistence.FlushModeType; @Repository public class AccountDao { private ProductionJPAAccess productionJpaAccess; public Account get(String accountNumber) { return productionJpaAccess.get(Account.class, accountNumber); } public void save(Account account) { productionJpaAccess.save(account); } public void update(Account account) { //System.out.println(productionJpaAccess.getEntityManager().getFlushMode()); //productionJpaAccess.getEntityManager().setFlushMode(FlushModeType.COMMIT); productionJpaAccess.update(account); productionJpaAccess.getEntityManager().flush(); } @Inject public void setProductionJpaAccess(ProductionJPAAccess productionJpaAccess) { this.productionJpaAccess = productionJpaAccess; } }
service: TradeService
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.inject.Inject; @Service public class TradeService { private final Logger logger = LoggerFactory.getLogger(TradeService.class); private AccountDao accountDao; private DataConverter dataConverter; @Inject private GenIdentityDao genIdentityDao; public Account get(String accountNumber) { return accountDao.get(accountNumber); } @Transactional(value = "transactionManager", propagation=Propagation.REQUIRED) public synchronized void handleSimple(String accountNumber, double amount, boolean deposit) throws ResourceNotFoundException, NotEnoughException { // logger.error("{}, enter, {}", Thread.currentThread().getName(), this.hashCode()); Account account = get(accountNumber); if (account == null) { throw new ResourceNotFoundException("account(" + accountNumber + ") not found"); } double score = account.getScore(); if (deposit) { account.setScore(convert(account.getScore() + amount)); } else { if (account.getScore() < amount) { logger.error("--------------------" + Thread.currentThread().getName() + " " + accountNumber + ":" + score + (deposit ? "+" : "-") + amount + " failed"); throw new NotEnoughException("account(" + accountNumber + ") not enough"); } account.setScore(convert(account.getScore() - amount)); } // account.setCas(account.getCas() + 1); accountDao.update(account); logger.error("-----" + Thread.currentThread().getName() + " " + accountNumber + ":" + score + (deposit ? "+" : "-") + amount + "=" + account.getScore()); } public double convert(double value) { long lg = Math.round(value * 100); double d = lg / 100.0; return d; } @Transactional(value = "transactionManager") public void saveAccount(Account account) { accountDao.save(account); } @Inject public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Inject public void setDataConverter(DataConverter dataConverter) { this.dataConverter = dataConverter; } }
用了多线程模拟多个请求,
task:MultipleTask
import java.util.Random; public class MultipleTask implements Runnable { private TradeService tradeService; public MultipleTask(TradeService tradeService) { this.tradeService = tradeService; } @Override public void run() { String accountNumber = "SYS5"; Random r = new Random(); double amount = r.nextDouble() * 10; amount = convert(amount); boolean deposit = r.nextBoolean(); try { Thread.sleep(r.nextInt(100)); } catch (InterruptedException e) { } try { tradeService.handleSimple(accountNumber, amount, deposit); } catch (ResourceNotFoundException e) { e.printStackTrace(); } catch (NotEnoughException e) { e.printStackTrace(); } } public double convert(double value) { long lg = Math.round(value * 100); double d = lg / 100.0; return d; } }
测试类:
@Test public void testMultiple() throws Exception { for (int i = 0; i < 30; i++) { MultipleTask m = new MultipleTask(tradeService); new Thread(m, i + "").start(); } Thread.sleep(1000000000); }
特别要注意:
1.一定要在accountDao.update()方法的最后加上立即flush的语句,防止因没及时flush,其他线程访问到提交前的数据。
productionJpaAccess.getEntityManager().flush();
2.一定要在handleSimple方法里重新从数据库load对象,判断是否存在(已被别的线程删除?)、是否足够(已被别的线程取掉了一些钱?),然后更新。即让重新load,各种判断在synchronized管制之内。
如果这几个判断在synchronized管制之外,有可能A线程在判断通过后,等待B线程执行synchronized代码,然后A进入synchronized代码时,数据已经被改变了,即原来的判断就不正确了。
3.在test方法中加上 Thread.sleep(1000000000);是为了防止主线程停止后spring容器被关闭。
测试结果:
2014-04-24 13:35:22,399 [2] ERROR TradeService - -----2 SYS5:2.5+1.11=3.61 2014-04-24 13:35:22,413 [29] ERROR TradeService - -----29 SYS5:3.61-1.12=2.49 2014-04-24 13:35:22,414 [15] ERROR TradeService - --------------------15 SYS5:2.49-9.33 failed 2014-04-24 13:35:22,417 [27] ERROR TradeService - -----27 SYS5:2.49+4.06=6.55 2014-04-24 13:35:22,420 [23] ERROR TradeService - -----23 SYS5:6.55+1.61=8.16 2014-04-24 13:35:22,424 [17] ERROR TradeService - -----17 SYS5:8.16+3.82=11.98 2014-04-24 13:35:22,426 [7] ERROR TradeService - -----7 SYS5:11.98+9.48=21.46 2014-04-24 13:35:22,431 [21] ERROR TradeService - -----21 SYS5:21.46+2.91=24.37 2014-04-24 13:35:22,434 [5] ERROR TradeService - -----5 SYS5:24.37-4.36=20.01 2014-04-24 13:35:22,436 [19] ERROR TradeService - -----19 SYS5:20.01+2.95=22.96 2014-04-24 13:35:22,439 [3] ERROR TradeService - -----3 SYS5:22.96-2.72=20.24 2014-04-24 13:35:22,442 [11] ERROR TradeService - -----11 SYS5:20.24-2.25=17.99 2014-04-24 13:35:22,444 [25] ERROR TradeService - -----25 SYS5:17.99+3.97=21.96 2014-04-24 13:35:22,447 [13] ERROR TradeService - -----13 SYS5:21.96+6.31=28.27 2014-04-24 13:35:22,450 [9] ERROR TradeService - -----9 SYS5:28.27+3.7=31.97 2014-04-24 13:35:22,454 [28] ERROR TradeService - -----28 SYS5:31.97-4.84=27.13 2014-04-24 13:35:22,460 [26] ERROR TradeService - -----26 SYS5:27.13+2.29=29.42 2014-04-24 13:35:22,464 [8] ERROR TradeService - -----8 SYS5:29.42-1.73=27.69 2014-04-24 13:35:22,466 [0] ERROR TradeService - -----0 SYS5:27.69-9.4=18.29 2014-04-24 13:35:22,469 [16] ERROR TradeService - -----16 SYS5:18.29+2.03=20.32 2014-04-24 13:35:22,472 [22] ERROR TradeService - -----22 SYS5:20.32+2.58=22.9 2014-04-24 13:35:22,475 [24] ERROR TradeService - -----24 SYS5:22.9-3.39=19.51 2014-04-24 13:35:22,477 [1] ERROR TradeService - -----1 SYS5:19.51+2.27=21.78 2014-04-24 13:35:22,480 [14] ERROR TradeService - -----14 SYS5:21.78-8.68=13.1 2014-04-24 13:35:22,485 [10] ERROR TradeService - -----10 SYS5:13.1-2.81=10.29 2014-04-24 13:35:22,488 [18] ERROR TradeService - -----18 SYS5:10.29-1.59=8.7 2014-04-24 13:35:22,491 [4] ERROR TradeService - -----4 SYS5:8.7-2.44=6.26 2014-04-24 13:35:22,523 [12] ERROR TradeService - --------------------12 SYS5:6.26-6.56 failed 2014-04-24 13:35:22,526 [20] ERROR TradeService - -----20 SYS5:6.26+6.03=12.29 2014-04-24 13:35:22,532 [6] ERROR TradeService - -----6 SYS5:12.29+7.46=19.75
以上是自己实现的同步控制,适于只部署一台服务器。
如果是分布式环境,多个tomcat,用synchronized就没法控制了。
这时候就需要使用hibernate version,乐观锁。
hiberate里使用乐观锁很简单,在entity上加一个long类型的属性version,加上@Version,get,set。。
@Version @Column(name = "Version") private long version;
然后就可以正常的save,update了。
在更新时会自动加上 , version = ?, update XX set XXXX where id=?, version = ?
如果记录已经被更新了,version就会加1,就找不到这条记录更新不了了,会抛出OptimisticLockException异常。
@Test public void testVersion() { Account a = new Account(); a.setAccountNumber("SYS7"); a.setScore(10d); tradeService.saveAccount(a); // a = tradeService.get("SYS7"); System.out.println(a.getVersion()); for (int i = 0; i < 5; i++) { a.setScore(a.getScore() + 1); tradeService.update(a); //a = tradeService.get("SYS7"); System.out.println(a.getVersion()); } }
以上代码会抛出javax.persistence.OptimisticLockException异常,因为update后,数据库的version已经更新了,但a还没更新。所以得重新get出来。
@Test public void testVersion() { Account a = new Account(); a.setAccountNumber("SYS7"); a.setScore(10d); tradeService.saveAccount(a); a = tradeService.get("SYS7"); System.out.println(a.getVersion()); for (int i = 0; i < 2; i++) { a.setScore(11d); tradeService.update(a); a = tradeService.get("SYS7"); System.out.println(a.getVersion()); } }
以上代码打印结果是
0
1
1
因为2次都是把score set成11d,其实值没变,update就没真正执行,也就不会使version增加。
@Test public void testVersion() { Account a = new Account(); a.setAccountNumber("SYS7"); a.setScore(10d); tradeService.saveAccount(a); a = tradeService.get("SYS7"); System.out.println(a.getVersion()); for (int i = 0; i < 2; i++) { a.setScore(a.getScore() + 1); tradeService.update(a); a = tradeService.get("SYS7"); System.out.println(a.getVersion()); } }
以上代码打印
0
1
2
是正常的。
相关推荐
用户密码登录,存钱、取钱、转账、销户 动态内存vector
c语言下实现模拟ATM机的过程,包括存钱取钱、查询、积分等,可以同时管理5个账户
取钱:卡是否存在,是否冻结,取钱金额是否正确 2.转账:把一个卡里的钱转到其他卡内 (卡是否存在,是否冻结,对方账户是否存在,转账的 金 额是否正确) 3.改密:(1)原密码改密 (2)身份证改密 4.解卡:判断卡号和身份证...
putmoney 方法用于存钱,getmoney 方法用于取钱。这些方法会修改账户的余额。 showbalance 方法 showbalance 方法用于显示当前账户的余额。 Main 方法 Main 方法是程序的入口点,用于启动程序。在 ...
基于java的网上银行系统,可以存钱,取钱,注册等等一系列操作
JAVA_银行存款取款.doc
在VC++6.0中编写一个控制台程序,模拟开户、销户、存钱、取钱的过程
SWING写的界面,SQL2000数据库,对初学都来说很有用.
一个VB写的消费记录管理程序,VB-access版存钱取钱记录管理系统,在编写时主要用到了ADO、工具栏,带图标的菜单栏、状态栏,ADOX创建新表,导出到Excel等方面的知识,因此你可参考本程序的话,会学习到相关的知识...
简单银行账号管理系统 可以创建账号 删除账号 取钱存钱 转账等。
c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 c语言网络银行 ...
1、模拟公司会计与出纳行为,会记收账并往公司账户存钱,出纳从公司同一账户取钱用于开支,假定公司只有一个出纳和一个会计。编程实现会计与出纳行为。 2、不采取同步措施情况下,记录会计与出纳分别对账户进行一次...
小规模企业纳税,在第一次申报税的时候,需要上传的文件,社会保险费缴费人存款账户账号报告表
主要介绍了Java使用锁解决银行取钱问题,结合实例形式分析了java线程同步与锁机制相关原理及操作注意事项,需要的朋友可以参考下
操作系统小作业,完善了网上文档中的代码。100个线程对单个银行账户进行的存取操作,并未涉及到并发和互斥。我上传的另外一个资源中涉及到了同步和互斥的问题,可以参考下。
Java设计一个银行帐户类,成员变量包括账号、储户姓名、开户时间、身份证号码、存款余额等帐户信息,成员方法包括存款、取款操作。
其中包括办理业务、存取钱操作。输入相关信息,显示办理、存钱情况
一个人可以有好几个活期储蓄账户,一个活期账户包括id(账号)、balance(余额 )、rate(年利率),还包括show(显示账户信息)、deposit(存钱)、withdraw(取钱)、settle(结算利息)等操作。故设计一个类...
Java存钱代码
编写一个程序,用来模拟银行帐户的基本操作,如帐户开户的话,则最低存款额为100、存取现金操作以及在使用任意修改余额后都可以随时查看帐户余额。请使用重载的带参数的构造函数。 提示:要实现此问题的解决方案,请...