Java为什么会发生死锁

死锁:一组互相竞争资源的线程互相等待,导致“永久”阻塞的现象。举个简单的例子,线程T1执行账户A给账户B转账,线程T2执行账户B给账户A转账,线程T1执行的时候,先获得账户A的锁,然后再获得账户B的锁,线程T2执行的时候,先获得账户B的锁,再获得账户A的锁,如果同时执行线程T1和T2的话,会导致线程T1一直在等待获取账户B的锁,线程T2一直再等待获取账户A的锁,从而发生死锁。

代码一:代码一会出现死锁

class Account {
    private int balance;
    void trransfer(Account target, int amt) {
        // 锁定转出账户
        synchronized (this) {
            // 锁定转入账户
            synchronized( target) {
                if (this.balance > amt) {
                    this.balance -= amt;
                    target.balance += amt;
                }
            }
        }
    }
}

发生死锁的四个条件

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

如何预防死锁

互斥是锁的本质特性,所以互斥条件没有办法破坏。其他三个条件则可以破坏,进而避免死锁。具体操作如下:

破坏占用且等待

对于这个条件,可以一次性申请所有的资源。就拿前面我们转账的例子来说,转账需要的是两个资源:一个转出账号,一个转入账号。当这两个资源同时被申请时,该怎么处理?这个时候需要一个新的角色:Allocator,它的功能是:同时申请资源apply()和同时释放资源free()。下面给出代码二和代码三进行说明

代码二:定义总管角色

class Allocator {
    private List<Object> als = new ArrayList<>();
    // 一次性申请所有资源
    synchronized boolean apply(Object from, Object to) {
        if (als.contains(from) || als.contains(to)) {
            return false;
        } else {
            als.add(from);
            als.add(to);
        }
        return true;
    }
    // 释放资源
    synchronized void free(Object from, Object to) {
        als.remove(from);
        als.remove(to);
    }
}

代码三:

class Account {
    private Allocator actr;
    private int balance;
    // 转账
    void trransfer(Account target, int amt) {
        // 一次性申请转出账号和转入账号资源,直到成功
        while (!actr.apply(this, target));
        try {
            // 锁定转出账户
            synchronized (this) {
                // 锁定转入账户
                synchronized (target) {
                    if (this.balance > amt) {
                        this.balance -= amt;
                        target.balance += amt;
                    }
                }
            }
        } finally {
            actr.free(this, target);
        }
    }
}
破坏不可抢占条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放自己占有的资源。这一点synchronized是做不到的,因为synchronized申请资源申请不到的时候,线程会直接进入阻塞状态。虽然语言层面解决不了,但是Java.util.concurrent包下面的Lock可以解决。

破坏循环等待条件

破坏这个条件,需要对资源进行排序,然后按序申请资源。假设每个账户都有自己id,这个id可以作为排序字段,申请的时候按照从小到大的顺序来申请,这样就可以避免循环等待了。 具体如代码四所示:

class Account {
    private int id;
    private int balance;
    // 转账
    void trransfer(Account target, int amt) {
        Account left = this;
        Account right = target;
        if (this.id > target.id) {
            left = target;
            right = this;
        }
            // 锁定序号小的账号
            synchronized (left) {
                // 锁定序号大的账号
                synchronized (right) {
                    if (this.balance > amt) {
                        this.balance -= amt;
                        target.balance += amt;
                    }
                }
            }
    }
}

发表评论

电子邮件地址不会被公开。

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部