并发编程-破除死锁

203 阅读1分钟

前言

先说一下死锁的产生,假设一个场景:

账户A向账户B转账,与此同时,账户B也向账户A转账,代码如下,我们用细粒度锁,锁住多个互斥资源

    public void transfer(Account target, int amount) {
        synchronized (this) {
            synchronized (target) {
                this.balance -= amount;
                target.balance += amount;
            }
        }
    }

上面代码就可能出现如下死锁,通过有向图我们可以发现循环等待的情况:

出现的原因

只有当以下 4 个条件同时满足的情况下,才会出现死锁

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

解决方案

只要打破产生死锁的任意一个条件,就能避免发生死锁

  • 互斥 这个条件通常都不可避免发生
  • 占有且等待:可以一次性将所有互斥资源都申请到,再进行临界区操作
  • 不可抢占:使用 synchronzied 关键字,只能让线程本身去释放持有资源,外界无法进行干涉,也就是说很有可能会发生死等的情况,jdk 还提供有更灵活的锁机制 lock
  • 循环等待:例子中可以看到,两个线程是按照相反的顺序去申请资源,那么将多个线程申请资源的顺序统一就可以避免循环等待了