一个死锁的问题

北京-小北 2015-01-21 11:10:28
先描述下业务场景.
系统会生成单据号,单据号需要按一定规则连续递增. 方法A实现了这个规则.(最新单据号会存在数据库,A方法需要更新数据库)
系统多个模块都需要用到方法A, 所以在方法A用了synchronized 修饰.
各模块运行正常. 现在需求发生变更. 需要支持单据的批处理.
就出现以下场景.
当线程T1执行方法B进行批处理时,循环10次调用方法A,
线程T2执行方法C进行单条处理.
T1线程的B方法执行5次后,T2线程的C方法获取到了锁,开始执行,需要更新数据库,但是线程B方法事务还没提交,还占用了数据库的锁,方法C就开始等待. 这时方法B因为无法获得到A的锁,剩余次数无法执行,
程序陷入死锁.

求怎么解决?

目前的解决方案有 锁粗化.即将锁加在方法B,C上. 但是类似方法B,C这种,在几十个类中,改动非常大.
有没有简单点的方法呢?
...全文
429 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
北京-小北 2015-02-08
  • 打赏
  • 举报
回复
解决办法如下: 问题一:批量处理. 解决方法:将批量处理中每单条一个事务. 问题二.一个事务操作中需要2个单号,调用2次方法a 解决方法:新增方法d,生成指定数量的单号. 应用层a,d写锁互斥,数据库层用悲观锁锁行
windsunmoon 2015-01-23
  • 打赏
  • 举报
回复
引用 3 楼 dgqjava 的回复:
修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
我觉得是正解。方法a是一个单独的业务逻辑。
dgqjava 2015-01-23
  • 打赏
  • 举报
回复
修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
skyhitnow 2015-01-23
  • 打赏
  • 举报
回复
楼主可以考虑下用aop控制下,在这些方法上加锁或者类似锁的机制。 或者在获得A的锁后验证数据库条件,得不到锁的话就进入等待状态,释放先前得到的锁。
skyhitnow 2015-01-23
  • 打赏
  • 举报
回复
为什么不给方法A传入一个参数:生成单据号的数量?
a12939026 2015-01-23
  • 打赏
  • 举报
回复
你的A方法的逻辑就是生成连续的单号? 那用一个sequence不就解决问题了么。 干嘛搞这么复杂。
dgqjava 2015-01-23
  • 打赏
  • 举报
回复
引用 7 楼 qq315737546 的回复:
[quote=引用 6 楼 dgqjava 的回复:] [quote=引用 5 楼 qq315737546 的回复:] [quote=引用 3 楼 dgqjava 的回复:] 修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
方法a是一个生成单号的方法, 单号需要连续. 如果事务单独控制, 那么方法b如果出现回滚(b的业务回滚), 方法a也要回滚. 且c方法可能已经在a回滚前的基础上进行了操作(因为a是单独事务),所以方法b回滚后, a方法还不能回滚,只能将b中用掉的单号单独保存,建立废号再利用机制. 这个改动貌似会更大.[/quote] 你的意思是你十个操作只要有一个失败十个都要回滚, 如果是这样的话那么你的问题不是死锁的问题, 就算程序中不出现死锁, 线程1执行5次a方法, 线程2执行1次a方法, 线程1再执行5次a方法并且失败了, 那么就算不存在死锁仍然会存在你现在说的无法回滚的问题, 而你所说的解决方案在b和c方法上加锁也是不现实的, 这样相当于将事务序列化执行, 并发性能是无法容忍的, 所以你的这个问题本身从设计上就是不对的, 一个批量处理, 相当于多次单条的处理, 每次成功就提交事务, 就算有失败的也不是全部回滚, 而应该最后提示: 成功xxx条, 失败xxx条[/quote] 是的.目前的解决办法是将批处理中的每条作为单独事务了. 但是引发了另外一个问题. 在某个方法d中, 需要生成2个单据号(一次操作,生成2张单据). 调用了2次方法a, 这种因为必须保证d在一个事务中,这样就也可能会存在上面说的问题. 请问这种情况怎么解决呢?[/quote] 这样的需求只能做成序列化事务, d方法和a方法公用一个锁
北京-小北 2015-01-23
  • 打赏
  • 举报
回复
引用 6 楼 dgqjava 的回复:
[quote=引用 5 楼 qq315737546 的回复:] [quote=引用 3 楼 dgqjava 的回复:] 修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
方法a是一个生成单号的方法, 单号需要连续. 如果事务单独控制, 那么方法b如果出现回滚(b的业务回滚), 方法a也要回滚. 且c方法可能已经在a回滚前的基础上进行了操作(因为a是单独事务),所以方法b回滚后, a方法还不能回滚,只能将b中用掉的单号单独保存,建立废号再利用机制. 这个改动貌似会更大.[/quote] 你的意思是你十个操作只要有一个失败十个都要回滚, 如果是这样的话那么你的问题不是死锁的问题, 就算程序中不出现死锁, 线程1执行5次a方法, 线程2执行1次a方法, 线程1再执行5次a方法并且失败了, 那么就算不存在死锁仍然会存在你现在说的无法回滚的问题, 而你所说的解决方案在b和c方法上加锁也是不现实的, 这样相当于将事务序列化执行, 并发性能是无法容忍的, 所以你的这个问题本身从设计上就是不对的, 一个批量处理, 相当于多次单条的处理, 每次成功就提交事务, 就算有失败的也不是全部回滚, 而应该最后提示: 成功xxx条, 失败xxx条[/quote] 是的.目前的解决办法是将批处理中的每条作为单独事务了. 但是引发了另外一个问题. 在某个方法d中, 需要生成2个单据号(一次操作,生成2张单据). 调用了2次方法a, 这种因为必须保证d在一个事务中,这样就也可能会存在上面说的问题. 请问这种情况怎么解决呢?
dgqjava 2015-01-23
  • 打赏
  • 举报
回复
引用 5 楼 qq315737546 的回复:
[quote=引用 3 楼 dgqjava 的回复:] 修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
方法a是一个生成单号的方法, 单号需要连续. 如果事务单独控制, 那么方法b如果出现回滚(b的业务回滚), 方法a也要回滚. 且c方法可能已经在a回滚前的基础上进行了操作(因为a是单独事务),所以方法b回滚后, a方法还不能回滚,只能将b中用掉的单号单独保存,建立废号再利用机制. 这个改动貌似会更大.[/quote] 你的意思是你十个操作只要有一个失败十个都要回滚, 如果是这样的话那么你的问题不是死锁的问题, 就算程序中不出现死锁, 线程1执行5次a方法, 线程2执行1次a方法, 线程1再执行5次a方法并且失败了, 那么就算不存在死锁仍然会存在你现在说的无法回滚的问题, 而你所说的解决方案在b和c方法上加锁也是不现实的, 这样相当于将事务序列化执行, 并发性能是无法容忍的, 所以你的这个问题本身从设计上就是不对的, 一个批量处理, 相当于多次单条的处理, 每次成功就提交事务, 就算有失败的也不是全部回滚, 而应该最后提示: 成功xxx条, 失败xxx条
北京-小北 2015-01-23
  • 打赏
  • 举报
回复
引用 3 楼 dgqjava 的回复:
修改事务传播特性, 方法a在任何情况下都应该重新起一个事务, 而不应该加入已有的事务, 因为方法a本身是一个专门处理单据号的独立的业务逻辑, 这样程序锁和数据库锁的粒度保持一致, 就不会有你现在的问题
方法a是一个生成单号的方法, 单号需要连续. 如果事务单独控制, 那么方法b如果出现回滚(b的业务回滚), 方法a也要回滚. 且c方法可能已经在a回滚前的基础上进行了操作(因为a是单独事务),所以方法b回滚后, a方法还不能回滚,只能将b中用掉的单号单独保存,建立废号再利用机制. 这个改动貌似会更大.
  • 打赏
  • 举报
回复
做好线程同步控制,应该可以解决楼主的问题。

67,550

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧