java进账和转账需要锁吗,Spring与Java中的锁
两种情况分析:1、@Transactional+Synchronized。2、AOP+锁。扩展:分布式锁
point:文章中的内容本人并没有在实际中用过也没有测试过,只是想到了这个问题所以按自己目前的理解做一个记录,正确性待验证。
@Transactional+Synchronized
在使用Spring的@Transactional处理事务时虽然有事务隔离机制,但是在SERIALIZABLE以下的隔离级别并不能解决并发情况下的幻读、脏读问题。看下面的一个例子:
@Transactional
public void update() {
boolean index = select(....);
if(index) {
update( ...index = false...,);
doSomthing()
}
}
@Transactional默认的隔离级别是可重复读,按道理说是没有脏读、不可重复读等问题的,但是细想一下,假如以上代码是为了保证doSomthing()只被执行一次。如果两个线程对应的事务A和事务B都进行了select操作(因为该隔离级别下读不阻塞),返回都为true。然后事务A进行了update并执行了doSomthing()。当A执行完updat,B又会接着往下执行,会再执行一次doSomthing()。
所以就需要业务层来实现严格的同步,Java中锁实现有Synchronized和ReentrantLock两种,代码可能是这个样子的:
private ReentrantLock lock = new ReentrantLock();
@Transactional
public void update() {
lock.lock();
try {
boolean index = select(....);
if(index) {
update( ...index = false...,);
doSomthing()
}
}finally {
lock.unlock();
}
}
------------------------
@Transactional
public synchronized void update() {
boolean index = select(....);
if(index) {
update( ...index = false...,);
doSomthing()
}
}
不管是Synchronized还是ReentrantLock,其实这样写都还是不能保证同步,因为Spring的@Transactional是AOP实现的,它是在函数开始前开启事务,在函数执行完毕后提交事务。所以上面代码中锁的释放是在事务提交之前,所以还是会有前面提到的问题。
AOP+锁
上面提到的锁无效是因为锁的范围是在AOP事务范围之内,那针对这个的解决方案可以使用AOP+锁实现,我们可以自定义一个锁注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLock {
}
然后定义一个AOP切面:
@Component
@Aspect
public class LockAspect implements Ordered {
private static ReentrantLock lock = new ReentrantLock();
@Pointcut("@annotation(MyLock)")
public void dolock(){}
@Around("dolock()")
public void testLock(ProceedingJoinPoint joinPoint) {
lock.lock();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
...
}finally {
lock.unlock();
}
}
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //小的先执行
}
}
然后我们就可以这样实现锁同步:
@MyLock
@Transactional
public synchronized void update() {
boolean index = select(....);
if(index) {
update( ...index = false...,);
doSomthing()
}
}
分布式锁
上面的锁实现在分布式环境下都是无效的,因为分布式下lock不是一个对象。我了解的解决方案有下面几个:
1、自己通过数据库实现锁
既然reentranLock和Synchronized不能使用的原因是锁对象不是同一个,那是不是可以通过数据库来实现同一个锁对象?在数据库中定义一个锁表,插入一个状态记录(比如0/1、true/false),当线程读取数据库记录是0的时候就视为获取锁,并将状态修改为1。当业务操作执行完要释放锁的时候就将状态改回0。或者直接利用数据库的唯一性约束,设置唯一主键,每次获取锁的操作都是一个insert操作,因为唯一性约束所以只会有一个线程insert成功,即获得锁,释放锁时delete该条记录就行。
this.SQL_TRY_ACQUIRE_LOCK = "update " + props.getTableName() + " set locked = true ,token = ? ,start_time = ? ,expire_time = ? ,end_time = null
where id = ? and (locked = false or expire_time < ?) ";
this.SQL_SELECT_LOCK = "select id ,name ,locked ,token ,start_time ,end_time ,expire_time ,attributes from " + props.getTableName() + "
where id = ? ";
public DLock acquireLock(DLockType type, String lockToken, long currTime, long expireTime) {
this.template.update(this.SQL_TRY_ACQUIRE_LOCK, new Object[]{lockToken, new Timestamp(currTime), new Timestamp(expireTime), type.getId(), new Timestamp(currTime)});
List locks = this.template.query(this.SQL_SELECT_LOCK, new Object[]{type.getId()}, this.rowMapper);
if (locks != null && !locks.isEmpty()) {
DLock lock = (DLock)locks.get(0);
if (lockToken.equals(lock.getLockToken())) {
return lock;
} else {
long sysCurrTime = System.currentTimeMillis();
if (!lock.isLocked() || lock.getExpireTime() != null && lock.getExpireTime() >= currTime) {
return null;
} else {
logger.debug("LockExpireTime={}, CurrTime={}, SysCurrTime={}", new Object[]{lock.getExpireTime(), currTime, sysCurrTime});
return lock;
}
}
} else {
return null;
}
}
以上代码先update,如果锁空闲就可以update成功,并且update的时候数据库锁会保证其他线程不能操作该条数据。
2、利用缓存如redis实现
3、利用zookeeper实现
java进账和转账需要锁吗,Spring与Java中的锁相关推荐
- @Transactional事务中使用锁坑(@Transactional事务中使用锁失效)
@Transactional事务中使用锁失效 说明: Spring中使用注解@Transactional作事务管理,@Transactional注解在方法上时,是方法完成之后才进行提交事务的 测试代码 ...
- mysql 高并发写入锁表_使用mysql中的锁解决高并发问题
阿里云产品通用代金券,最高可领1888分享一波阿里云红包. 阿里云的购买入口 为什么要加锁 多核计算机的出现,计算机实现真正并行计算,可以在同一时刻,执行多个任务.在多线程编程中,因为线程执行顺序不可 ...
- 锁失效_关于bigtable中chubby锁失效时的一点思考
最近跟国内几家热门公司做分布式存储的大佬们聊了聊,过程十分愉快,但同时也有点小虐.说到底,自己在这个领域并没有很久的经验,很多东西仍停留在知其然而不知其所以然的地步.魔鬼藏在细节之处. 不过这也正好是 ...
- 不属于linux内核锁的是,Linux内核中的锁
1. 为什么要保证原子性 处理器分两种:cisc(复杂指令集,可以直接在内存上进行操作,如x86,一条汇编指令可以原子的完整读内存.计算.写内存)和rics(精简指令集,所有操作都必须是在CPU内部进 ...
- java aop面试_我想知道Spring在面试中应该怎么介绍,以及如何介绍他的aop?
Spring是一个开源框架,它由Rod Johnson创建.它是为了解决企业应用开发的复杂性而创建的.Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情.然而,Spring的用途 ...
- java mvc 批量插入_请教mysql spring mvc +mybatis中批量插入的问题?
dao实现文件中函数: @Override public int insertContentList( List list) { Map params = createMap(); params.pu ...
- Java中的锁原理、锁优化、CAS、AQS详解
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:景小财 www.jianshu.com/p/e674ee68 ...
- Spring Boot Redis 实现分布式锁,真香!!
之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...
- Spring Boot Redis 实现分布式锁,真香
之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...
- Java中的锁[原理、锁优化、CAS、AQS]
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 作者:高广超 链接:https:/ ...
最新文章
- 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)
- 鼠标滚轮事件及解决滚轮事件多次触发问题
- 在FF与IE中使用数据岛
- 太突然!一日本上班族大叔被通知得了诺贝尔奖,他却选择消失了16年,又有重大发现!...
- 前端(jQuery)(10)-- jQuery标签切换
- 你为什么选择计算机应用专业,致新生!我为什么选择信息工程系
- HDU 4031 Attack(线段树/树状数组区间更新单点查询+暴力)
- 我碰到的到现在为止,还没有找到比较好的解决方法的sps问题
- Linux 以form表单形式上传文件
- win10哪个版本打游戏好?win10游戏性能分析
- 记录vant weapp 小程序组件库遇到的坑以及ios和安卓兼容问题 SubmitBar
- Bundle Adjustment — A Modern Synthesis(一)
- [论文阅读] (22)图神经网络及认知推理总结和普及-清华唐杰老师
- 85 R 银行信用卡风控评分数据分析
- DHCP服务配置-Cisco模拟器
- Windows环境下安装pkg-config
- html页面转盘如何实现,html5制作转盘的详解及实例
- (1146, Table 'django.django_session' doesn't exist)
- 【经验分享】为什么视频画面解码失败之后显示的是绿幕?
- 黑马视频~rocketMq
热门文章
- lightoj 1029 最小生成树 + 最大生成树
- 手把手教你在Windows下搭建React Native Android开发环境
- Head First Design Patterns(深入浅出设计模式)-设计模式介绍
- Mybatis之插入ListT
- 关于 LimitedConcurrencyLevelTaskScheduler 的疑惑
- ubuntu安装dockers和images:dvwa
- inner join ,left join ,right join区别
- Android消息推送(Android Push Notification)
- win8下Oracle 12c 创建新用户并分配表空间
- HTML5手机游戏将迎美好未来 .