怎么去解决死锁的问题

1.不小心死锁了怎么办?

1.1什么是死锁?

在使用线程的使用过程中,我们无法避免加锁的情况,那么加锁就可能会导致死锁。如果出现了死锁的情况,我们需要怎么去解决呢?首先,我们来看下什么是死锁,下面这张图很形象的说明了什么是死锁,路口的四辆车都分别等待对面车道的车让行,相互等待,形成了死锁的状态。在线程中,死锁是这样定义的,一组相互竞争相同资源的线程因为相互等待而导致永久阻塞,这种现象就叫做死锁。

在这里插入图片描述

1.2模拟死锁示例

用一个转账的示例来模拟死锁。

账户类:

public class Account {
    private String accountName;
    private int balance;

    public Account(String accountName, int balance) {
        this.accountName = accountName;
        this.balance = balance;
    }

    public void debit(int amount) {
        this.balance -= amount;
    }

    public void cribit(int amount) {
        this.balance += amount;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }
}

转账类:

public class TransferAccount implements  Runnable{
    private Account fromAccount; // 转入账户
    private Account toAccount; // 转出账户
    private int amount;

    public TransferAccount(Account fromAccount, Account toAccount, int amount) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }

    @Override
    public void run() {
        while(true){
            synchronized (fromAccount){
                synchronized (toAccount){
                    if(fromAccount.getBalance()>=amount){
                        fromAccount.debit(amount);
                        toAccount.cribit(amount);
                    }
                }
                System.out.println(fromAccount.getAccountName()+"--------------"+fromAccount.getBalance());
                System.out.println(toAccount.getAccountName()+"--------------"+toAccount.getBalance());
            }
        }
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Account fromAccount = new Account("zhangsan",100000);
        Account toAccount = new Account("lisi",200000);

        Thread a = new Thread(new TransferAccount(fromAccount,toAccount,1));
        Thread b = new Thread(new TransferAccount(toAccount,fromAccount,1));

        a.start();
        b.start();
    }
}

当我们运行测试类后,会发现当执行到某个时候,控制台会停止输出,这个时候就是发生了死锁。

在这里插入图片描述

1.3发生死锁的原因

发生死锁必需以下四个条件同时满足

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

1.4如何解决死锁问题

上面说到发生死锁必须同时满足四个条件,那么,我们破坏其中一个条件,就可以解决死锁的问题。如果已经发生了死锁,一般没有什么好的方法来解决,只能通过重启应用,所以如果要解决死锁问题,最好的方式就是提前规避。
首先,我们肯定不能去破坏第一个条件,锁的作用就是要互斥。所以,只能去破坏其他三个条件。

1.4.1 解决方法一

破坏第二个条件,占有且等待,那么我们可以一次性去申请所有资源,就不会存在等待的问题了。

对上面的示例进行改进:

新增类Allocator :

public class Allocator {
    private List<Object> list = new ArrayList<>();

    /**
     * 申请资源的方法
     * @param from
     * @param to
     * @return
     */
    synchronized boolean apply(Object from,Object to){
        if(list.contains(from) || list.contains(to))return false;
        list.add(from);
        list.add(to);
        return true;
    }

    /**
     * 释放资源的方法
     * @param from
     * @param to
     * @return
     */
    synchronized void free(Object from,Object to){
        list.remove(from);
        list.remove(to);
    }
}

修改TransferAccount类:

public class TransferAccount01 implements  Runnable{
    private Account fromAccount; // 转入账户
    private Account toAccount; // 转出账户
    private int amount;
    Allocator allocator;

    public TransferAccount01(Account fromAccount, Account toAccount, int amount,Allocator allocator) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
        this.allocator = allocator;
    }

    @Override
    public void run() {
        while(true){
            if(allocator.apply(fromAccount,toAccount)){ // 都会在这个地方去获取资源
                try {
                    synchronized (fromAccount) {
                        synchronized (toAccount) {
                            if (fromAccount.getBalance() >= amount) {
                                fromAccount.debit(amount);
                                toAccount.cribit(amount);
                            }
                        }
                        System.out.println(fromAccount.getAccountName() + "--------------" + fromAccount.getBalance());
                        System.out.println(toAccount.getAccountName() + "--------------" + toAccount.getBalance());
                    }
                }finally {
                    allocator.free(fromAccount,toAccount);
                }
            }
        }
    }
}

测试类修改:

public class Test {
    public static void main(String[] args) {
        Account fromAccount = new Account("zhangsan",100000);
        Account toAccount = new Account("lisi",200000);
        Allocator allocator = new Allocator(); // 统一分配锁
        Thread a = new Thread(new TransferAccount01(fromAccount,toAccount,1,allocator));
        Thread b = new Thread(new TransferAccount01(toAccount,fromAccount,1,allocator));

        a.start();
        b.start();
    }
}

经过这样处理之后就不会再出现死锁的问题了。

1.4.2 解决方法二

破坏不可抢占,可以让占用部分资源的线程进一步申请资源,如果申请不到,可以主动释放其他线程占用的资源,这样就可以把不可抢占的条件破坏掉。

修改TransferAccount类:

public class TransferAccount02 implements Runnable {
    private Account fromAccount; // 转入账户
    private Account toAccount; // 转出账户
    private int amount;
    Lock fromLock = new ReentrantLock();
    Lock toLock = new ReentrantLock();

    public TransferAccount02(Account fromAccount, Account toAccount, int amount) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }

    @Override
    public void run() {
        while (true) {
            if (fromLock.tryLock()) {
                if (toLock.tryLock()) {
                    if (fromAccount.getBalance() >= amount) {
                        fromAccount.debit(amount);
                        toAccount.cribit(amount);
                    }
                    System.out.println(fromAccount.getAccountName() + "--------------" + fromAccount.getBalance());
                    System.out.println(toAccount.getAccountName() + "--------------" + toAccount.getBalance());

                }
            }
        }
    }
}

测试类修改:

public class Test {
    public static void main(String[] args) {
        Account fromAccount = new Account("zhangsan",100000);
        Account toAccount = new Account("lisi",200000);
        Thread a = new Thread(new TransferAccount02(fromAccount,toAccount,1));
        Thread b = new Thread(new TransferAccount02(toAccount,fromAccount,1));
        a.start();
        b.start();
    }
}

1.4.2 解决方法三

破坏循环等待,可以按照顺序来进行资源的处理。

这里仅对TransferAccount进行修改,对传进来的账户的hashcode进行排序以保证顺序。

public class TransferAccount03 implements  Runnable{
    private Account fromAccount; // 转入账户
    private Account toAccount; // 转出账户
    private int amount;

    public TransferAccount03(Account fromAccount, Account toAccount, int amount) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
    }

    @Override
    public void run() {
        Account left = null;
        Account right = null;
        if(fromAccount.hashCode()>toAccount.hashCode()){
            left = toAccount;
            right = fromAccount;
        }
        while(true){
            synchronized (left){
                synchronized (right){
                    if(fromAccount.getBalance()>=amount){
                        fromAccount.debit(amount);
                        toAccount.cribit(amount);
                    }
                }
                System.out.println(fromAccount.getAccountName()+"--------------"+fromAccount.getBalance());
                System.out.println(toAccount.getAccountName()+"--------------"+toAccount.getBalance());
            }
        }
    }
}

但是在这个例子中会存在一个问题,最后变成总是从一个相同的账户转到另一个账号,最终有一个账户的余额为0.

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值