[Java并发编程实战]什么是锁顺序死锁

精诚所至,金石为开。———《后汉书·广陵思王荆传》
意思是人的诚心所到,能感动天地,使金石为之开裂。比喻只要专心诚意去做,什么疑难问题都能解决。

锁顺序死锁

两个线程试图通过不同的顺序获取多个相同的锁。如果请求的顺序不相同,那么会出现循环的锁依赖现象,产生死锁。但是如果保证同时请求锁L和锁M的每一个线程,都是按照从 L 到 M 的顺序,那么就不会发生死锁了。

举个例子说明一下,让我们更加直观的了解顺序死锁问题,请看下面代码:

public class ThreadDeadLockTest{

    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                doSomething();
            }
        }
    }

    public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                doSomething();
            }
        }
    }
}

如果一个线程调用了 leftRight, 另一个线程调用了 rightLeft,这样的交替运行,那么它们会发生死锁。线程 A 拿到了left锁,而线程 B 拿到了 right 锁,这个时候就陷入了相互等待的循环了。

[Java并发编程实战]什么是锁顺序死锁_第1张图片

解决的办法是,如果所有线程以通用的固定秩序获得锁,程序就不会出现锁顺序死锁问题了。

动态的锁顺序死锁

然而,有些时候并不是那么容易避免顺序死锁的发生。比如,下面这个代码:

public class ThreadDeadLockTest{

    public void transferMoney(Account fromAccount, Account toAccount, Amount money) {

        synchronized (fromAccount) {
            synchronized (toAccount) {
                if(fromAccount < money) {
                    throw new InsufficientFundsException();
                } else {
                    fromAccount.debit(money);
                    toAccount.credit(money);
                }
            }
        }
    }
}

针对上面程序,如果两个线程同时调用transferMoney, 一个从 A 向 B 转账,另一个从 B 向 A 转账,那么就会发生死锁。如下代码调用所示:

    transferMoney(myAccount, yourAccount, 10);
    transferMoney(yourAccount, myAccount, 20);

可以看出,锁的顺序就是参数的顺序,它超出了我们的控制。那么,有么有办法来制定一个锁的规范,保证锁的顺序是一致的呢?

答案是肯定的,现在我们制定一种锁的顺序,并在整个应用程序都遵循它。我们可以使用

System.identityHashCode

的方式,它返回 Object.hashCode, 唯一一个值。但是,我们还得想如何规避哈希值冲突产生的问题。哈希冲突,我们可以再引入一个锁,当哈希冲突的时候,必须获取这个锁,从而保证一次只有一个线程执行哈希冲突的情况。

请看下面代码,保证了锁顺序的一致,避免了死锁的发生。

public class ThreadTest{
    //额外锁,针对哈希值冲突的情况
    private static final Object tieLock = new Object();

    public void transferMoney(Account fromAccount, Account toAccount, Amount money) {

        class Helper{
            public void transfer() {
                if(fromAccount < money) {
                    throw new InsufficientFundsException();
                } else {
                    fromAccount.debit(money);
                    toAccount.credit(money);
                }
            }
        }
        //或许哈希值
        int fromHashCode = System.identityHashCode(fromAccount);
        int toHashCode = System.identityHashCode(toAccount);

        if(fromHashCode < toHashCode) {
            synchronized (fromAccount) {
                synchronized (toAccount) {
                    new Helper().transfer();
                }
            }
        } else if(fromHashCode > toHashCode) {
            synchronized(toAccount) {
                synchronized(fromAccount) {
                    new Helper().transfer();
                }
            }
        } else {
            synchronized(tieLock) {
                synchronized(fromAccount) {
                    synchronized(toAccount) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }
}

上面程序就保证了锁顺序的一致性,从而规避了锁顺序死锁的情况。很多时候不易察觉这种动态的锁顺序,我们需要更加细心的使用多个锁,尽早的制定锁顺序规范。

你可能感兴趣的:(Java并发编程,Java并发编程实战,并发编程,锁顺序死锁,死锁,java)