Java通过锁的顺序避免死锁

java INFO院院长 30℃ 0评论

例子

银行账户转账问题,两个用户转账的话,如果采用一般的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的锁

Java通过锁的顺序避免死锁

注:转账双发的账户放生了反转,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的区别

转载请注明:INFO院 » Java通过锁的顺序避免死锁

喜欢 (0)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址