17.预防死锁

在一些场景中,去预防死锁是可能的。在这个内容中我将会描述三种技术:

  1. 锁排序
  2. 锁超时
  3. 死锁检测
锁排序
当多个线程需要相同的锁但是以不同的顺序获取他们的时候死锁就会发生。
如果你确定所有的锁在任何线程中都是相同的顺序,死锁就不会发生。看这个例子:
Thread 1:

  lock A 
  lock B


Thread 2:

   wait for A
   lock C (when A locked)


Thread 3:

   wait for A
   wait for B
   wait for C

如果一个线程,像线程3,需要几个锁,它就必须在固定的顺序获取他们。它就不能在之后的序列中获取一个锁直到已经获取到更早的锁。
例如,线程2和线程3都不能锁住C,直到他们首先锁住A。因为线程1持有了锁A,线程2和线程3必须首先等待直到锁A释放了。然后他们成功锁住A,在他们尝试去锁A或者B之前。
锁顺序是一个简单然而有效的死锁预防机制。然而,它只是在如果你知道所有的锁顺序的情况下使用。并不是所有的场景都是这样的。
锁超时
另外一个死锁预防机制就是在锁获取上设置一个超时,意味着尝试获取一个锁的线程将会只是尝试这些时间在放弃之前。如果一个线程在给予的超时时间内没有成功获取所有需要的锁,它将会倒退,释放所有获取的锁,等待一个随机的时间之后然后再尝试。等待的随机时间可以作为给予其他尝试获取相同锁的线程一个机会去获取所有的锁,并且因此应用程序继续运行不会锁住。
这里有一个例子,两个线程尝试获取相同的锁在不同的顺序中,这个线程将会倒退并且重试。
Thread 1 locks A
Thread 2 locks B

Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked

Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.

在上面的这个例子中,线程2将会重试获取这个锁大概200毫秒在线程1之前,并且很可能的成功获取这两个锁。 已经尝试获取锁A的线程1然后等待。当线程2结束后,线程1将会能够获取这两个锁(除非线程1或者其他的线程在这个期间获取这个锁了)。
需要记住的一个问题是,只是因为一个锁超时了,就不需要意味着这个线程死锁了。它也只是意味着正在持有锁的这个线程(因为其他线程超时的)将会花时间去完成它的任务。

另外,如果足够多的线程竞争相同的资源,他们将会冒险尝试一次又一次的同时获取这个线程,甚至如果超时以及回退了。在重试之前有两个线程每一个等待时间在0到500毫秒之间的话,这个可能不会发生,但是如果10或者20个线程这个情况就不同了。然后在重试之前两个线程等待相同时间的可能性就会高很多了。

伴随着锁超时的一个问题就是,在java中不可能在同步代码块中设置一个超时时间。你将会不得不创建一个自定义的锁类或者使用java5中并发包中的一个。写自定义锁不难,但是超出了这篇文章的范围。在java并发路径后面的内容将会覆盖自定义锁。

死锁检测

死锁检测是一个更巨大的死锁预防机制,目的在于锁顺序不可能设置的,并且锁超时也不可行的。

每一次一个线程携带一个锁,它在一个线程和锁的数据结构中被记住的。另外的,一个线程无论什么时候需要一个锁,它也会被记在这个数据结构中。

当一个线程需要一个锁,但是这个请求被拒绝了,这个线程可能会遍历锁图去检查死锁。例如,如果一个线程A需要锁7,但是锁7被线程B持有,然后线程A会检查是否线程B已经请求线程A持有的锁的任何一个。如果线程B已经请求了,一个死锁已经发生了(线程A持有锁1,请求锁7,线程B持有锁7,请求锁1)。

当然一个死锁的场景可能会比两个线程彼此持有锁更加复杂。线程A可能等待线程B,线程B等待线程C,线程C等待线程D,并且线程D等待线程A。为了使得线程A检测一个死锁,它就必须过渡的检查所有被线程B需要的锁。来自线程B的请求的锁,线程A将会到达线程C,并且然后到达线程D,它就发现是线程A自己持有的一个锁。然后它就知道一个死锁已经发生了。

下面是一个锁获取的图示,并且被4个线程需要(A,B,C和D)。一个数据结构像这样就可以用来检测死锁。


那么如果一个死锁被检测到了,这个线程会做什么呢?

一个可能的动作就是去释放所有的锁,回退,等待一个随机数量的时间然后重试。这个跟更简单的锁超时机制更加相似除了当一个死锁发生的时候线程只是回退。不只是因为他们的锁请求超时。然而,如果许多的线程正在竞争相同的锁,他们可能重复的以死锁结束,甚至如果他们回退并且等待。

一个更好的选项就是决定或者分配一个线程的优先级以至于只有一个(或者一些)线程回退。剩下的线程继续获取他们需要的锁因为没有死锁发生的话。如果被分配到线程的优先级被固定的话,相同的线程将会总是被分配更高的优先级。为了避免这个,你可能随机的分配优先级无论死锁什么时间被检测到。


翻译地址:http://tutorials.jenkov.com/java-concurrency/deadlock-prevention.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值