前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java之美-死锁

Java之美-死锁

作者头像
kk大数据
发布2020-09-23 14:35:33
4270
发布2020-09-23 14:35:33
举报
文章被收录于专栏:kk大数据kk大数据kk大数据

不点蓝字,我们哪来故事

今天是雨天,淅淅沥沥,但依然浇不灭学习 Java 的热情。

今天谈一谈死锁,那还是从最经典的例子:转账开始说起。

程序即生活,程序的世界是生活的映射,但是人类是很智能的,轻轻松松处理任何复杂的问题,以至于有些细节我们都忽略不计。

但在程序的世界,任何一个细节都是代码构建的,下面我们来体会一下古人的转账如何体现在程序世界。

转账中的死锁

在古代,没有信息化,账户的存在真的就是一个账本,每个账户都有一个账本,这些账本统一放在文件架上。

掌柜在给我们做转账的时候,要去文件架上同时拿到转出账本和转入账本,然后才能做转账,掌柜在拿账本的时候,可能遇到下面三种情况:

  1. 文件架上恰好有转出账本和转入账本,那就同时拿走;
  2. 如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上已有的账本拿到手,同时等着别的柜员把另外一个账本送回来;
  3. 转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。

上面这个过程在编程的世界如何实现?

其实用两把锁就实现了,转出账本一把,转入账本一把。

如下图,在 transfer() 方法中,我们首先尝试锁定转出账户 ,再尝试锁定转入账户,只有当两者都成功时,才执行转账操作。

用代码实现也很简单:

class Account {
    private int balance;
    void transfer(Account target,int amt) {
        synchronize(this) {
            synchronize(target) {
                if (this.balance > amt ) {
                    this.balance -= amt;
                    target.balance += amt;
                }
            }
        }
    }
}

但是天下没有免费的午餐,这是种很典型的死锁写法,哈哈,什么情况下会产生死锁呢?

如果客户找柜员张三做个转账业务:账户A 转 账户 B 100 元,此时另一个客户找柜员李四也做个转账业务:账户 B 转 账户 A 100元。

于是张三和李四同时都去文件架上拿账本,这时候可能凑巧张三拿到了账本 A,李四拿到了账本 B。

张三拿到 A 后就等着 账本 B (账本 B 被李四拿走了),而李四拿到账本 B 后就等着账本 A (账本A被张三拿走了),他们要等多久呢?

他们会永远等下去 ... 因为张三不会把账本送回去,李四也不会把账本送回去。

那这就是编程领域的死锁了。

如果给死锁一个专业的定义,就是:

一组互相竞争资源的线程因互相等待,导致“永久阻塞”的现象。

死锁发生的条件

既然编程中用到锁的地方,有很有可能发生死锁,那么是不是可以总结一个通用的产生死锁的条件。

有牛人已经准备好了,以下四个条件同时出现时,才会产生死锁:

  • 互斥,共享资源 X 和 Y 只能被一个线程占用
  • 占有且等待,线程 T1 取得共享资源 X 在等待资源 Y 的时候,不释放共享资源 X;
  • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
  • 循环等待,线程 T1 等待 线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源。

只要破坏其中一个,就可以避免死锁发生。

如何避免死锁

(1)首先互斥这个条件是没法破坏的,因为我们设计锁的初衷为的就是互斥。

(2)占有且等待,我们可以一次性申请所有资源,也就是一次性占有资源 X 和 资源 Y 。

在程序中,我们可以用一个公有的单例的角色来管理资源,它的作用就是同时申请资源和同时释放资源。

(3)不可抢占,占有部分资源的线程进一步申请其他资源时,如果申请不到,就把自己占有的资源释放掉

在 Java 中,synchronize 是做不到主动释放资源的,因为在申请资源的时候,如果申请不到,线程就直接进入阻塞状态了。

但是,Lock 是可以轻松解决这个问题的。

(4)循环等待,可以把资源排序,按照顺序占有,这样线性化之后,自然就不存在循环了。

循环等待这个条件,我们可以在申请资源之前,就把资源排好序,有序申请,即可。

如何查看死锁

如果不可避免要用到多个锁,并且可能已经产生死锁了,我们要如何检测?

首先可以使用 jps 或者系统的 ps 命令,确定进程的 id

然后,使用 jstack 获取线程栈:

${JAVA_HOME}\bin\jstack pid

可以看到两个进程,互相在等待对方的锁id,如果是简单的情形,还会直接显示有死锁的情况。

最后

非必要情况,尽量不使用多个锁,并且有需要时,才持有锁,否则即使是非常精通的工程师,也会掉坑里去。嵌套的 synchronize 或 lock 非常容易出问题。

如果非要必须使用多个锁,尽量设计好锁的获取顺序。

使用带超时的方法,为程序带来更多的可控性。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 KK架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 死锁发生的条件
  • 如何避免死锁
  • 如何查看死锁
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档