线程死锁操作的一般情况都是,等待拿到某一个锁来进行操作或者说某一个资源,如果一直拿不到的话,那么就一直阻塞,导致程序无法正常结束或者终止.

有一个非常经典的问题可以说明这个现象(哲学家吃饭问题),5个哲学家去吃饭,坐在一张圆桌旁,

他们有5根筷子,并且每两个人中间放一根筷子,哲学家们时而思考,时而进餐,每个人都需要一双筷子才能吃到东西,并在吃完后将筷子放回原处继续思考。

一般情况下,每个人都迅速的拿到自己左边的筷子,然后尝试拿右边的筷子,但是同时不放下自己手上的筷子,而是等待其他人放下他的筷子,这就产生了死锁。 有一些管理筷子的算法,能够使每个人都吃到东西。比如 我拿到了筷子之后,尝试拿另外一根筷子时发现其他人已经拿走了,我就放弃自己手中的筷子,并且过段时间后在去尝试。

这样的做法在性能上有一定的损耗。我们先来看下面这段程序,银行帐户之间转账。

1 public static void transferMoney(final AccountUser a1 , final AccountUser a2, final double money) {
 2         synchronized (a1) {
 3             synchronized (a2) {
 4                 // do something.
 5                 if (a2.getBalance()  -  money > 0) {
 6                     a2.setBalance(a2.getBalance()  -  money);
 7                     a1.setBalance(a1.getBalance()  +  money);
 8                 } else {
 9                     throw new RuntimeException("Error");
10                 }
11             }
12         }
13 }

假设 现在有这样一个情况,A用户向B用户转账,同时B用户向A用户转账.

这种加锁的顺序,很可能会导致死锁。这种情况称为Lock-Ordering Deadlock(锁顺序死锁). 线程1 先拿到A用户的锁,线程2 拿到B用户锁,线程1等着拿B用户的锁,线程2等着拿A用户的锁,他们如果不进行协调的话,那么他们就会一直等着,导致转账无法正常完成。

我们可以通过控制拿锁的顺序来避免这种情况,将程序修改成下面这种方式

public static void transferMoney(final AccountUser a1 , final AccountUser a2, final double money) {
        int fromHash = System.identityHashCode(a1);
        int toHash = System.identityHashCode(a2); 
        if (fromHash > toHash) {
            synchronized (a1) {
                synchronized (a2) {
                    // do something.
                    if (a2.getBalance()  -  money > 0) {
                        a2.setBalance(a2.getBalance()  -  money);
                        a1.setBalance(a1.getBalance()  +  money);
                    } else {
                        throw new RuntimeException("Error");
                    }
                }
            }
        } else if (fromHash < toHash) {
            synchronized (a2) {
                synchronized (a1) {
                    // do something.
                    if (a2.getBalance()  -  money > 0) {
                        a2.setBalance(a2.getBalance()  -  money);
                        a1.setBalance(a1.getBalance()  +  money);
                    } else {
                        throw new RuntimeException("Error");
                    }
                }
            }
        } else {
            synchronized (lock) {
                synchronized (a1) {
                    synchronized (a2) {
                        // do something.
                        if (a2.getBalance()  -  money > 0) {
                            a2.setBalance(a2.getBalance()  -  money);
                            a1.setBalance(a1.getBalance()  +  money);
                        } else {
                            throw new RuntimeException("Error");
                        }
                    }
                }
            }
        }
    }

通过hashCode的值来简单的判断,从而避免死锁发生的可能性。如果出现A转账给A的情况,hashCode一样的话,那么我们就在加一个额外的锁来控制他。

 如果我们是从A -> b 那么,他们的hashcode将不一样,线程首先获得的lock是A对象,并且同时有B ->A  那么,这个线程获得的锁也将是A 所以就会造成阻塞效果。 

对于java的锁顺序的控制,来避免死锁的情况发生,除了上述的方法之外。 我们可以采用其他的策略来进行控制,例如 采用上面的主动放弃自己拥有的锁的策略, 如果线程a 拿到了userA的锁,同时还需要获取其他的锁,我们可以让线程a尝试的去获取另外的锁,如果没有获取到那么我们大方一些让出自己的锁,同时让自己休息一段时间,在重新去获取UserA(自己的第一把锁),再次尝试获取其他的锁。 这样我们就可以让出CPU等资源。

我们考虑另外一种方案,我们结合两种策略(hashCode + 自动释放锁),如果线程a 和线程b都在获取同一些锁的时候,我们让线程A自己主动放弃,过段时间在去获取锁。