关于死锁,面试的一切都在这里了

网站建设4年前发布
29 0 0

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力作用,它们都将无法推进下去。,20230306130739f4ff28a6886f4638169404ad95bbf78f5f29c4974,产生死锁的四个必要条件得烂熟于心:,相应的,如果想在程序运行之前预防发生死锁(也成为 “死锁预防”),必须设法破坏产生死锁的四个必要条件之一,光看罗列出来的几点文字肯定还是不能完全理解,下面会结合实例来给大伙解释。,用 Java 写一个死锁,这绝对是面试中 Java 手写题的 TOP2!!!除了人尽皆知的手写单例模式,手写死锁可能有些小伙伴会遗漏掉。,逻辑其实非常简单,我们申请两个资源,开两个线程,每个线程持有其中的一个资源,并且互相请求对方的资源,就构成了死锁。,2023030613074051c6bb6005a27b43a632677bff59b4669c080e344,MySQL 经典的死锁案例,下面来看个 MySQL 经典的死锁案例:转账,A 账户给 B 账户转账 50 元的同时,B 账户也给 A 账户转账 30 元,2023030613074092044802415e457dabe963157983b8bab34388806,正常情况下,如果只有一个操作,A 给 B 转账 50 元,可以在一个事务内完成,先获取 A 用户的余额和 B 用户的余额,因为之后需要修改这两条数据,所以需要通过写锁(for UPDATE)锁住他们,防止其他事务更改导致我们的更改丢失而引起脏数据,20230306130741777444d93c5c4b9640e506445318f9331bf213366,20230306130947050a2bb603c65e62a17270758c860eb920a4c5405,但如果 A 给 B 转账和 B 给 A 转账同时发生,那就是两个事务,可能发生死锁:,1)A 用户给 B 用户转账 50 元,需在程序中开启事务 1 来执行 SQL,获取 A 的余额同时锁住 A 这条数据。,20230306130742d58a164902ba3418ad640590a0756e97baf49d647,2)B 用户给 A 用户转账 30 元,需在程序中开启事务 2 来执行 SQL,并获取 B 的余额同时锁住 B 这条数据。,2023030613074206d2a2b7728b616e848220e5d388a9f54cafcc682,3)在事务 1 中执行剩下的 SQL,此时事务 1 是获取不到 B 的锁的,也即 select for update 就会被阻塞住;,2023030613094796f4e0e4948b3575d795326b6b5ec1600bbfd5774,4)同理,事务 2 继续执行剩下的 SQL,请求 A 的锁,也是获取不到的,事务 1 和事务 2 存在相互等待获取锁的过程,导致两个事务都挂起阻塞,最终抛出获取锁超时的异常。,20230306130744693a05622f1f112dc56781479b802fdece2c0e223,要想解决上述死锁问题,我们可以从死锁的四个必要条件入手。,指导思想其实很明确:就是保证 A 向 B 转账和 B 向 A 转账这两个事务同一时刻只能有一个事务能成功获取到锁,由于互斥和不剥夺是锁本质的功能体现,无法修改,所以咱们从另外两个条件尝试去解决。,1)破坏 “请求和保持” 条件:A 和 B 之间的操作用同一个锁锁住(比如用 Redis 分布式锁做,A 和 B 之间的锁的 key 表示为 A:B,可以让 id 小的用户排在前面,id 大的用户排在后面,这样来设计 key。如果存在分库分表的情况,用 hashcode 来做比较也行),保证 A 向 B 转账和 B 向 A 转账这两个事务同一时刻只能有一个事务能成功获取锁,20230306130744389d0ff661589ac404d639148664206d9cff6d534,2)破坏 “循环等待” 条件:先获取更小的锁,获取到了小的锁才能获取大锁(所谓小锁还是大锁,也可以简单的根据用户的 id 来进行区分,先请求用户 id 较小的,再请求用户 id 较大的)。比如 A.id < B.id,那么 A 和 B 之间的操作,都是要先获取 A 锁,再获取 B 锁,202303061307445237f3f29cae51b9a7a024f236842dffc05184272,具体代码可参考如下:, 2023030613094892569ef7781314285f5785d5cfff888cf8a05e706

© 版权声明

相关文章