Java通过锁的顺序避免死锁

本文探讨了银行账户转账过程中可能出现的死锁问题及其解决方案。通过分析死锁产生的四个必要条件,介绍了如何利用对象的identityHashCode确定锁的顺序,从而有效避免死锁的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

例子

银行账户转账问题,两个用户转账的话,如果采用一般的synchronized嵌套的话,容易造成死锁,现在我们通过类似哲学家问题的解决方案一样:先获取同一个锁,才有资格获取下一个。而判断是通过System.identityHashCode()来生成类的hashcode()的返回值作为唯一标识,相同的话,我们再加一把锁。

// 死锁版本
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public void debit(int amount) {
        System.out.println("after debit " + amount + " " + this.money + " -> " + (this.money-amount));
        this.money -= amount;
    }

    public void credit(int amount) {
        System.out.println("after credit " + amount + " " + this.money + " -> " + (this.money+amount));
        this.money += amount;
    }

    public int get() {
        return this.money;
    }
}

public class OrderLock {
    private static final Object tieLock = new Object();

    public void transferMoney(final Account fromAcct, final Account toAcct, final int amount) 
            throws InsufficientResourcesException {
        class Helper {
            public void transfer() throws InsufficientResourcesException {
                if (fromAcct.get() < amount) 
                    throw new InsufficientResourcesException();
                else {
                    fromAcct.debit(amount);
                    toAcct.credit(amount);
                }
            }
        }

        // 两个用户使用这两个账户给对方转账时,死锁;因为一方fromAcct账户为对方的toAcct账户
        synchronized (fromAcct) {
            synchronized (toAcct) {
                new Helper().transfer();
            }
        }
    }

    class MyThread implements Runnable {
        private Account fromAcct;
        private Account toAcct;
        private int amount;

        public MyThread(Account fromAcct, Account toAcct, int amount) {
            this.fromAcct = fromAcct;
            this.toAcct = toAcct;
            this.amount = amount;
        }


        @Override
        public void run() {
            try {
                transferMoney(this.fromAcct, this.toAcct, this.amount);
            } catch (InsufficientResourcesException e) {
                System.out.println("操作失败");
            }
        }

    }

    public static void main(String[] args) {
        Account fromAcct = new Account(100);
        Account toAcct = new Account(230);
        OrderLock orderLock = new OrderLock();
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            if ((i & 1) == 0)
                threadPool.execute(orderLock.new MyThread(fromAcct, toAcct, 10));
            else threadPool.execute(orderLock.new MyThread(toAcct, fromAcct, 10));
        }
    }
}
// 解决死锁版本
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public void debit(int amount) {
        System.out.println("after debit " + amount + " " + this.money + " -> " + (this.money-amount));
        this.money -= amount;
    }

    public void credit(int amount) {
        System.out.println("after credit " + amount + " " + this.money + " -> " + (this.money+amount));
        this.money += amount;
    }

    public int get() {
        return this.money;
    }
}

public class OrderLock {
    private static final Object tieLock = new Object();

    public void transferMoney(final Account fromAcct, final Account toAcct, final int amount) 
            throws InsufficientResourcesException {
        class Helper {
            public void transfer() throws InsufficientResourcesException {
                if (fromAcct.get() < amount) 
                    throw new InsufficientResourcesException();
                else {
                    fromAcct.debit(amount);
                    toAcct.credit(amount);
                }
            }
        }

        // 转账双方共用这两个账户的对象,否则无法通过下面方式排序下面的锁顺序
        int fromHash = System.identityHashCode(fromAcct);
        int toHash = System.identityHashCode(toAcct);

        if (fromHash < toHash) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    new Helper().transfer();
                }
            }
        } else if (fromHash > toHash) {
            synchronized (toAcct) {
                synchronized (fromAcct) {
                    new Helper().transfer();
                }   
            }
        } else {
            synchronized (tieLock) {
                synchronized (fromAcct) {
                    synchronized (toAcct) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }

    class MyThread implements Runnable {
        private Account fromAcct;
        private Account toAcct;
        private int amount;

        public MyThread(Account fromAcct, Account toAcct, int amount) {
            this.fromAcct = fromAcct;
            this.toAcct = toAcct;
            this.amount = amount;
        }


        @Override
        public void run() {
            try {
                transferMoney(this.fromAcct, this.toAcct, this.amount);
            } catch (InsufficientResourcesException e) {
                System.out.println("操作失败");
            }
        }

    }

    public static void main(String[] args) {
        // 转账双方共用这两个账户对象
        Account fromAcct = new Account(100);
        Account toAcct = new Account(230);
        OrderLock orderLock = new OrderLock();
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            if ((i & 1) == 0)
                threadPool.execute(orderLock.new MyThread(fromAcct, toAcct, 10));
            // 注:转账的账户变成了toAcct,被转账的账户变成了fromAcct
            else threadPool.execute(orderLock.new MyThread(toAcct, fromAcct, 10));
        }
    }
}

关于上面代码的疑惑

1 嵌套synchronized 代码块

    synchronized (fromAcct) {
            synchronized (toAcct) {
                new Helper().transfer();
            }   
    }

理解:嵌套的synchronized是按照嵌套的顺序获取锁,即先获取最外层的锁fromAcct,再获取toAcct的锁。

2 fromAcct和toAcct的锁

这里写图片描述

注:转账双发的账户放生了反转,Jack的toAcct变成了Bob的fromAcct

死锁的四个必要条件

  1. 互斥使用(资源独占)
    一个资源每次只能给一个进程使用

  2. 不可抢占(不可剥夺)
    资源申请者不能强行的从资源占有者手中夺取资源,资源 只能由占有者自愿释放

  3. 请求和保持(部分分配,占有申请)
    一个进程在申请新的资源的同时保持对原有资源的占有(只要这样才是动态申请,动态分配)

  4. 循环等待
    存在一个进程等待队列{P1, P2, … ,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,星辰给一个进程等待环路

hashCode和identityHashCode

  • hashCode()方法时Object类下面的一个方法,供继承类重写,根据对象内存地址计算哈希值,String类重写了hashCode方法,并改为根据字符序列计算哈希值。

  • identityHashCode()方法是System类中的静态方法,根据对象内存地址来计算哈希值

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println("s1的hashCode值::" + s1.hashCode());
        System.out.println("s2的hashCode值::" + s2.hashCode());
        System.out.println("s1的identityHashCode值::" + System.identityHashCode(s1));
        System.out.println("s2的identityHashCode值::" + System.identityHashCode(s2));
        System.out.println("--------------------------");
        String s3 = "Java";
        String s4 = "Java";
        System.out.println("s3的hashCode值::" + s3.hashCode());
        System.out.println("s4的hashCode值::" + s4.hashCode());
        System.out.println("s3的identityHashCode值::" + System.identityHashCode(s3));
        System.out.println("s4的identityHashCode值::" + System.identityHashCode(s4));
    }

// print
s1的hashCode值::99162322
s2的hashCode值::99162322
s1的identityHashCode值::817899724
s2的identityHashCode值::397836821
--------------------------
s3的hashCode值::2301506
s4的hashCode值::2301506
s3的identityHashCode值::1326857436
s4的identityHashCode值::1326857436

参考

Java通过锁的顺序避免死锁
hashCode和identityHashCode的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值