这几天在练习针对一个账号进行存钱、取钱操作的并发控制。
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
是正常的。
相关推荐
在 PowerBuilder 中,数据窗口(DataWindow)是用于与数据库交互的重要组件,它支持多种数据库操作,包括并发控制。并发控制是确保多用户在同时访问相同数据时,不会导致数据不一致性的关键机制。关系型数据库系统...
用户密码登录,存钱、取钱、转账、销户 动态内存vector
总的来说,"Hibernate+Swing写的银行存钱系统"是一个涵盖多方面技术的学习项目,涵盖了Java GUI编程、数据库操作、ORM技术、事务管理等多个核心知识点,对于初学者而言,是深入理解Java编程和数据库应用的绝佳实践。
c语言下实现模拟ATM机的过程,包括存钱取钱、查询、积分等,可以同时管理5个账户
取钱:卡是否存在,是否冻结,取钱金额是否正确 2.转账:把一个卡里的钱转到其他卡内 (卡是否存在,是否冻结,对方账户是否存在,转账的 金 额是否正确) 3.改密:(1)原密码改密 (2)身份证改密 4.解卡:判断卡号和身份证...
操作系统是计算机系统的核心组成部分,它负责管理系统的硬件资源和软件资源,为应用程序提供...实验二并发与调度是一个很好的实践平台,它将理论知识与实际编程相结合,帮助学习者巩固并深化对操作系统基本原理的理解。
putmoney 方法用于存钱,getmoney 方法用于取钱。这些方法会修改账户的余额。 showbalance 方法 showbalance 方法用于显示当前账户的余额。 Main 方法 Main 方法是程序的入口点,用于启动程序。在 ...
实验目的是理解和掌握并行/并发、同步/异步的概念,并通过实现一个模拟银行账户的程序来具体应用这些概念。 首先,理解并行/并发是关键。并行是指多个任务在同一时刻实际地同时执行,而并发则是指多个任务在一段...
Java网上银行系统是一个利用Java编程语言开发的,为用户提供在线存款、取款、注册等一系列金融服务的应用程序。这个系统的核心目标是模拟真实银行的业务流程,为用户带来便捷、安全的虚拟金融体验。在本文中,我们将...
Struts2 主要负责MVC(模型-视图-控制器)架构的实现,提供了一种组织应用程序逻辑的方式,而Hibernate 是一个对象关系映射(ORM)工具,用于简化数据库操作。这个“struts2整合hibernate的网上银行模拟项目”结合了...
根据给定的文件信息,我们可以总结出以下关于Java编程语言中的...通过上述分析,我们可以看出这是一个简单的银行账户管理系统示例,通过面向对象的编程方式实现了基本的存款和取款功能,并通过控制台与用户进行交互。
事务是一组数据库操作,它代表了一个业务逻辑的完整执行单元。在数据库中,事务由一系列读写操作组成,这些操作要么全部执行,要么全都不执行,以确保数据的准确性和一致性。事务的概念与程序有所不同,程序通常包含...
这通常通过并发控制算法实现,如两阶段锁、多版本并发控制(MVCC)等。这些算法确保事务在执行时互不见面,直到一个事务提交后,其他事务才能看到其更改,这称为隔离性。 ACID(原子性、一致性、隔离性、持久性)是...
假设有一个银行转账的例子,事务A为取款事务,事务B为存款事务。如果这两个事务没有正确地被管理,就可能引发问题。例如: - 在T1时刻,事务A开始,事务B也在T2时刻开始。 - T3时刻,事务A查询账户余额为800;T4...
每次只有一个线程能够进入`lock`代码块,从而避免了数据竞争。 此外,作者提到了另一个资源,其中涉及了同步和互斥问题。这可能是对上述问题的一种解决方案,使用了更高级的同步原语或者线程间通信技术,比如`...
Struts是Java Web开发中的一个MVC(Model-View-Controller)框架,而Hibernate则是一个对象关系映射(ORM)工具,用于处理数据库操作。 首先,让我们深入了解一下Struts框架。Struts通过提供一个结构化的模型,使得...
在用户登录成功后,系统会显示一个简洁的首页,首页上会有清晰的菜单选项,包括“存钱”、“取钱”和“查看余额”等按钮。 存钱功能是该应用的核心之一。用户点击“存钱”按钮后,系统会跳转到存钱界面。在这个界面...
在本项目中,“基于struts+hibernate的模拟银行转账”是一个利用经典的Java Web开发框架Struts1和持久层框架Hibernate构建的简单银行系统。这个系统旨在模拟网上银行的基本功能,如账户管理、转账操作等,从而帮助...
其中,滚积数交易是一个关键点,因为它涉及到每日余额的更新,而这可能会与用户的解约操作冲突。 在处理这种并发问题时,我们可以采用以下几种方法: 1. **更新游标**:使用更新游标可以锁定记录,防止其他进程...
当一个线程在执行过程中需要等待其他线程完成特定操作(比如,账户余额不足需要等待存款)时,它会调用`wait()`释放锁并进入等待状态;而其他线程完成相应操作后,通过调用`notify()`唤醒等待的线程。 除了基本的...