分支-死锁和活锁详解

一、死锁的形成

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

实际工作中,我们经常听到死锁,死锁到底是什么东西呢?那么接下来就用实际例子演示一下银行转账功能。

用户转账的实体类:

/**
 * @Author nanjunkai
 * @Description: 用户转账的实体类
 * @Date 2020/12/1 18:08:10
 */
public class UserAccount {
    private final String name;//账户名称
    private int money;//账户余额

    // 显示锁
    private Lock lock = new ReentrantLock();

    public Lock getLock(){
        return lock;
    }

    public UserAccount(String name, int money) {
        this.name = name;
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public int getMoney() {
        return money;
    }

    // 转入金额
    public void addMoney(int money){
        this.money = money+money;
    }

    // 转出金额
    public void flyMoney(int money){
        this.money = money-money;
    }
    
}
银行转账接口:
public interface ITransfer {

    /**
     * @description: TODO
     * @date: 2020/12/1 18:39:30
     * @param from 转出账户
     * @param to 转入账户
     * @param money 转账金额
     * @return void
     * @throws InterruptedException
     */
    void transfer(UserAccount from, UserAccount to, int money) throws InterruptedException;
}

实现类:首先演示理想状态下,我们先锁转入账户,然后再锁转出账户

public class TransferAccount implements ITransfer {
    /**
     * @param from  转出账户
     * @param to    转入账户
     * @param money 转账金额
     * @return void
     * @description: 转账
     * @date: 2020/12/1 18:39:30
     */
    @Override
    public void transfer(UserAccount from, UserAccount to, int money) throws InterruptedException {
        synchronized (from) {//先锁转出
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(100);
            synchronized (to) {//再锁转入
                System.out.println(Thread.currentThread().getName());
                from.flyMoney(money);
                to.addMoney(money);
            }
        }
    }
}

模拟银行转账的功能:

public class PayCompany {

    // 执行转账动作
    private static class TransferThread extends Thread {
        private String name;// 线程名字
        private UserAccount from;// 转出账户
        private UserAccount to;// 转入账户
        private int money;// 转账金额
        private ITransfer iTransfer;// 实际转账动作


        public TransferThread(String name, UserAccount from, UserAccount to, int money, ITransfer iTransfer) {
            this.name = name;
            this.from = from;
            this.to = to;
            this.money = money;
            this.iTransfer = iTransfer;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            try {
                iTransfer.transfer(from, to, money);//转账操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        UserAccount zhangsan = new UserAccount("张三", 1000);
        UserAccount lisi = new UserAccount("李四", 2000);

        ITransfer iTransfer = new TransferAccount();

        TransferThread zhangsantolisi = new TransferThread("张三转李四", zhangsan, lisi, 200, iTransfer);
        TransferThread lisitozhangsan = new TransferThread("李四转张三", lisi, zhangsan, 200, iTransfer);

        zhangsantolisi.start();
        lisitozhangsan.start();

    }
}

运行结果:只有两条结果,并且程序没有结束(死锁)

李四转张三
张三转李四

按代码逻辑看,应该是四条,一个转账有两条结果(一个转出一个转入)。但是实际只有两条,所以考虑到是死锁的原因,先看看死锁的持久情况。

1.先输入jps

2.输入jstack id

结果发现真的是产生了死锁。发生死锁的原因:当张三转账给李四时,张三锁住了自己,准备去拿李四的锁,但是刚好李四拿到自己的锁,也准备去拿张三的锁,结果到最后谁都拿不到锁。

二、解决死锁的方法

1.通过hash值确实账户的顺序。(上面实例死锁原因还有一点,动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。

重新写一个实现类:identityHashCode(返回对象的hashCode)

public class SafeOperate implements ITransfer {
    private static Object tieLocK = new Object();//如果hash值相等,直接锁该对象

    @Override
    public void transfer(UserAccount from, UserAccount to, int money) throws InterruptedException {

        int fromHash = System.identityHashCode(from);//返回对象的hashCode
        int toHash = System.identityHashCode(to);

        if (fromHash < toHash) {
            synchronized (from) {//先锁转出
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(100);
                synchronized (to) {//再锁转入
                    System.out.println(Thread.currentThread().getName());
                    from.flyMoney(money);
                    to.addMoney(money);
                }
            }
        } else if (fromHash > toHash) {
            synchronized (to) {//先锁转出
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(100);
                synchronized (from) {//再锁转入
                    System.out.println(Thread.currentThread().getName());
                    from.flyMoney(money);
                    to.addMoney(money);
                }
            }
        } else {
            synchronized (tieLocK) {
                synchronized (from) {//先锁转出
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(100);
                    synchronized (to) {//再锁转入
                        System.out.println(Thread.currentThread().getName());
                        from.flyMoney(money);
                        to.addMoney(money);
                    }
                }
            }
        }
    }
}

main方法接口实现类修改,其他不变:

 ITransfer iTransfer = new SafeOperate();

运行结果:

张三转李四
张三转李四
李四转张三
李四转张三

结果正常,也是线程安全的。

2.通过tryLock解决死锁

实现类:

public class SafeOperateToo implements ITransfer {

    @Override
    public void transfer(UserAccount from, UserAccount to, int money) throws InterruptedException {
        long start = System.currentTimeMillis();
        Random random = new Random();
        while (true) {
            if (from.getLock().tryLock()) {
                try {
                    System.out.println(Thread.currentThread().getName());
                    if (to.getLock().tryLock()) {
                        try {
                            System.out.println(Thread.currentThread().getName());
                            from.flyMoney(money);
                            to.addMoney(money);
                            break;
                        } finally {
                            to.getLock().unlock();
                        }
                    }
                } finally {
                    from.getLock().unlock();
                }
            }
            Thread.sleep(random.nextInt(10));//可能会产生活锁
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

main方法接口实现类修改,其他不变:

 ITransfer iTransfer = new SafeOperateToo();

先把休眠代码注释运行,看下结果:

张三转李四
李四转张三
李四转张三
李四转张三
张三转李四
.........
李四转张三
304
张三转李四
张三转李四
305

其实只有两个人互相在转账,但是打印出了非常多的重复操作。为什么会出现这种情况?

三、活锁

活锁:当两者有一者拿到锁时,另一个同时也拿到锁时,就会造成谦让,从而导致尝试、失败....。

如何优化活锁?把休眠的代码打开,运行结果:

李四转张三
张三转李四
张三转李四
0
李四转张三
李四转张三
3

显而易见,尝试失败次数几乎没有了,时间也提升了很多倍。

四、总结

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

活锁:当两者有一者拿到锁时,另一个同时也拿到锁时,就会造成谦让,从而导致尝试、失败....

优化活锁:可以休眠一定时间来减少尝试和失败的次数。

死锁和活锁的区别?

死锁无法自行解开,而活锁可能自行解开。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值