动态顺序死锁及活锁示例

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44367006/article/details/99484677

这个示例模拟的是2个人相互转账的过程:
个人账户实体类

public class UserAccount {

    private final String name;//账户名称
    private int money;//账户余额

    private final Lock lock = new ReentrantLock();

    public Lock getLock() {
        return lock;
    }

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

    public String getName() {
        return name;
    }

    public int getAmount() {
        return money;
    }

    @Override
    public String toString() {
        return "UserAccount{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    //转入资金
    public void addMoney(int amount){
        money = money + amount;
    }

    //转出资金
    public void flyMoney(int amount){
        money = money - amount;
    }
}

转账动作接口

public interface ITransfer {
    void transfer(UserAccount from, UserAccount to, int amount)
    		throws InterruptedException;
}

不安全的转账动作实现
先锁转出方,再锁转入方看似没有问题。可如果当A转给B钱且获得A锁时,B也恰好转给A钱且获得B锁,不就形成死锁了吗?任何一方永远也不能同时得到A锁和B锁,程序无法运行下去,且很难一眼看出死锁问题。

public class TransferAccount implements ITransfer {
    @Override
    public void transfer(UserAccount from, UserAccount to, int amount)
    		throws InterruptedException {
        synchronized (from){
            System.out.println(Thread.currentThread().getName()
            		+" get"+from.getName());
            Thread.sleep(100);
            synchronized (to){
                System.out.println(Thread.currentThread().getName()
                		+" get"+to.getName());
                from.flyMoney(amount);
                to.addMoney(amount);
            }
        }
    }
}

模拟转账动作

public class PayCompany {

	/*执行转账动作的线程*/
    private static class TransferThread extends Thread{

        private String name;
        private UserAccount from;
        private UserAccount to;
        private int amount;
        private ITransfer transfer;

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


        public void run(){
            Thread.currentThread().setName(name);
            try {
                transfer.transfer(from,to,amount);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        UserAccount Jack = new UserAccount("Jack",20000);
        UserAccount Rose = new UserAccount("Rose",20000);
        ITransfer transfer = new TransferAccount();
//        ITransfer transfer = new SafeOperate();
//        ITransfer transfer = new SafeOperateToo();
        TransferThread JackToRose = new TransferThread("JackToRose"
                ,Jack,Rose,2000,transfer);
        TransferThread RoseToJack = new TransferThread("RoseToJack"
                ,Rose,Jack,4000,transfer);
        JackToRose.start();
        RoseToJack.start();

    }

}

结果:
在这里插入图片描述
不会产生死锁的安全转账方式一
强制定义顺序,如:
比较2个对象的哈希值,谁小先锁谁。如果一样,先抢第三把锁,拿到第三把锁的才能继续拿第一第二把锁。

public class SafeOperate implements ITransfer {

    private static Object tieLock = new Object();

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

        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);

        if(fromHash<toHash){
            synchronized (from){
                System.out.println(Thread.currentThread().getName()+" get "+from.getName());
                Thread.sleep(100);
                synchronized (to){
                    System.out.println(Thread.currentThread().getName()+" get "+to.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else if(toHash<fromHash){
            synchronized (to){
                System.out.println(Thread.currentThread().getName()+" get"+to.getName());
                Thread.sleep(100);
                synchronized (from){
                    System.out.println(Thread.currentThread().getName()+" get"+from.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else{
            synchronized (tieLock){
                synchronized (from){
                    synchronized (to){
                        from.flyMoney(amount);
                        to.addMoney(amount);
                    }
                }
            }
        }
    }
}

结果:
在这里插入图片描述
不会产生死锁的安全转账方式二
每一个UserAccount里加一个显式锁,进行循环尝试拿锁,只用同时拿到2个锁,才能执行操作。

public class SafeOperateToo implements ITransfer {

    @Override
    public void transfer(UserAccount from, UserAccount to, int amount)
            throws InterruptedException {
        Random r = new Random();
        while(true){
            if(from.getLock().tryLock()){
                System.out.println(Thread.currentThread().getName()
                        +" get"+from.getName());
                try{
                    if(to.getLock().tryLock()){
                        try{
                            System.out.println(Thread.currentThread().getName()
                                    +" get"+to.getName());
                            from.flyMoney(amount);
                            to.addMoney(amount);
                            System.out.println(from);
                            System.out.println(to);
                            break;
                        }finally{
                            to.getLock().unlock();
                        }
                    }
                }finally {
                    from.getLock().unlock();
                }

            }
            //Thread.sleep(r.nextInt(2));
        }

    }
}

结果:
在这里插入图片描述
可以看到,虽然最后完成了互相转账动作,但是尝试拿到2把锁的时间过长。这是因为这2个转账动作总是先尝试拿到自己的锁,然后都拿不到另一把锁,又都释放锁再次尝试。直到因为系统调度恰好有一边同时成功拿到2把锁。这种现象叫做活锁。

解决办法:每个线程休眠随机数,错开拿锁的时间。
结果:
在这里插入图片描述

没有更多推荐了,返回首页