一个死锁的问题

北京-小北 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这种,在几十个类中,改动非常大.
有没有简单点的方法呢?
...全文
421 11 打赏 收藏 转发到动态 举报
写回复
用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中用掉的单号单独保存,建立废号再利用机制. 这个改动貌似会更大.
  • 打赏
  • 举报
回复
做好线程同步控制,应该可以解决楼主的问题。
《计算机操作系统》可作为计算机硬件和软件以及计算机通信专业的本科生教材,也可作为从事计算机及通信工作的相关科技人员的参考书。 目录 第一章 操作系统引论 1.1 操作系统的目标和作用 1 1.1.1 操作系统的目标 1 1.1.2 操作系统的作用 2 1.1.3 推动操作系统发展的主要动力 4 1.2 操作系统的发展过程 5 1.2.1 无操作系统的计算机系统 5 1.2.2 单道批处理系统 6 1.2.3 多道批处理系统 7 1.2.4 分时系统 9 1.2.5 实时系统 11 1.2.6 微机操作系统的发展 12 1.3 操作系统的基本特性 14 1.3.1 并发性 14 1.3.2 共享性 15 1.3.3 虚拟技术 16 1.3.4 异步性 17 1.4 操作系统的主要功能 18 1.4.1 处理机管理功能 18 1.4.2 存储器管理功能 19 1.4.3 设备管理功能 21 1.4.4 文件管理功能 21 1.4.5 操作系统与用户之间的接口 22 1.5 OS结构设计 24 1.5.1 传统的操作系统结构 24 1.5.2 客户/服务器模式 26 1.5.3 面向对象的程序设计 27 1.5.4 微内核OS结构 29 习题 33 第二章 进 程 管 理 2.1 进程的基本概念 34 2.1.1 程序的顺序执行及其特征 34 2.1.2 前趋图 35 2.1.3 程序的并发执行及其特征 36 2.1.4 进程的特征与状态 37 2.1.5 进程控制块 41 2.2 进程控制 43 2.2.1 进程的创建 43 2.2.2 进程的终止 45 2.2.3 进程的阻塞与唤醒 46 2.2.4 进程的挂起与激活 47 2.3 进程同步 47 2.3.1 进程同步的基本概念 47 2.3.2 信号量机制 50 2.3.3 信号量的应用 53 2.3.4 管程机制 55 2.4 经典进程的同步问题 58 2.4.1 生产者—消费者问题 58 2.4.2 哲学家进餐问题 61 2.4.3 读者—写者问题 63 2.5 进程通信 65 2.5.1 进程通信的类型 65 2.5.2 消息传递通信的实现方法 66 2.5.3 消息传递系统实现中的若干问题 68 2.5.4 消息缓冲队列通信机制 69 2.6 线程 71 2.6.1 线程的基本概念 72 2.6.2 线程间的同步和通信 75 2.6.3 线程的实现方式 77 2.6.4 线程的实现 78 习题 81 第三章 处理机调度与死锁 3.1 处理机调度的层次 84 3.1.1 高级调度 84 3.1.2 低级调度 86 3.1.3 中级调度 87 3.2 调度队列模型和调度准则 88 3.2.1 调度队列模型 88 3.2.2 选择调度方式和调度算法的若干准则 90 3.3 调度算法 91 3.3.1 先来先服务和短作业(进程)优先调度算法 91 3.3.2 高优先权优先调度算法 93 3.3.3 基于时间片的轮转调度算法 95 3.4 实时调度 97 3.4.1 实现实时调度的基本条件 97 3.4.2 实时调度算法的分类 99 3.4.3 常用的几种实时调度算法 100 3.5 产生死锁的原因和必要条件 103 3.5.1 产生死锁的原因 103 3.5.2 产生死锁的必要条件 105 3.5.3 处理死锁的基本方法 105 3.6 预防死锁的方法 106 3.6.1 预防死锁 106 3.6.2 系统安全状态 107 3.6.3 利用银行家算法避免死锁 108 3.7 死锁的检测与解除 111 3.7.1 死锁的检测 111 3.7.2 死锁的解除 113 习题 114 第四章 存 储 器 管 理 4.1 存储器的层次结构 116 4.1.1 多级存储器结构 116 4.1.2 主存储器与寄存器 117 4.1.3 高速缓存和磁盘缓存 117 4.2 程序的装入和链接 118 4.2.1 程序的装入 118 4.2.2 程序的链接 120 4.3 连续分配方式 121 4.3.1 单一连续分配 121 4.3.2 固定分区分配 122 4.3.3 动态分区分配 123 4.3.4 伙伴系统 126 4.3.5 哈希算法 126 4.3.6 可重定位分区分配 127 4.3.7 对换 129 4.4 基本分页存储管理方式 130 4.4.1 页面与页表 130 4.4.2 地址变换机构 131 4.4.3 两级和多级页表 133 4.5 基本分段存储管理方

67,512

社区成员

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

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