在某些应用场景下,可以作到预防死锁的发生。本文会描述三种情形:html
多个线程须要相同的锁来完成代码顺畅运行,可是访问锁的顺序是不一样的,那么就可能会发生死锁的状况。java
也就是说,若是多个线程须要相同的锁,可是他们对锁的访问顺序是相同的,那么就不可能会出现死锁的状况。bash
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
复制代码
若是如今有一个线程,须要多个锁(和 Thread 3 相似),它必需要找既定的顺序去获取锁,它不能在没获取前面锁的状况下(例如:A)去获取排在后面的锁(例如:B)。数据结构
“锁排序”是一种简单而有效的死锁预防机制。可是,只有在获取任何锁以前就知道所需的全部锁才能使用这种方式防护。多线程
另外一个死锁预防机制是对尝试“获取锁”设置超时时长,这意味着尝试获取锁的线程只会在放弃以前阻塞这么长的时间。并发
若是一个线程在给定的超时时间内仍没有成功获取全部必要的锁,那么它将阻塞并释放全部已持有的锁,等待一段随机时间,而后重试。dom
等待的随机时间用于让其余线程尝试获取相同的锁,从而让应用程序继续运行而不阻塞。ui
下面的例子就是两个线程尝试以不一样的顺序获取相同的锁,而后线程阻塞和重试:spa
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.
复制代码
上例中,Thread 2 比 Thread 1 先 200ms 进行从新尝试而且在这种状况下很大可能会成功获取两个必要锁。Thread 1 将继续等待 lock A。当Thread 2运行结束,Thread 1 也可以获取两个锁。(除非Thread 2 或者其余线程在又开始插足锁得获取)线程
注:锁超时并不意味着线程已经死锁,也可能意味着持有锁的线程(致使另外一个线程超时)须要很长的时间来完成它的任务。
另外,若是有不少的线程竞争相同的资源,他们仍然有可能屡次的同时重试,即便超时和阻塞。这种状况在两个线程在重试以前等待0到500ms可能不会发生,可是若是是10到20个线程,那么状况就不同了。它和两个线程等待相用时间或者近乎相同的时间再重试的状况差很少。
可是,锁的超时机制并不能在Java的同步代码块中设置超时,你将不得不建立自定义锁类或使用java.util.concurrency包中的一个Java 5并发结构,编写自定义锁并不困难,但超出本文范围,有兴趣的自行了解。
死锁检测是一种较重的死锁防护机制,针对没法获取锁顺序和锁超时不可行的状况。
每当线程takes锁时,都会在线程和锁的数据结构中注明。另外,线程requests锁也会在数据结构中注明。
当线程请求锁的请求被拒绝时,线程能够遍历锁图来检查死锁。例如,若是线程A请求 lock 7,可是 lock 7被线程B持有,线程A会检测线程B是否请求线程A已经持有的锁(若是有)。若是线程B请求是线程A已经持有的锁,则发生死锁。
固然,大部分状况会更加复杂,每每都是多个线程形成死锁。
那么发现线程死锁该如何作呢?
一种可能的操做时释放全部锁,阻塞等待一段随机时间,而后重试。这和更简单的锁超时机制相似,只是线程仅在肯定发生死锁的时候进行阻塞,而不只根据发生锁超时来判断。可是,若是不少线程正在争夺相同的锁,即便它们阻塞并等待,它们也可能会重复地陷入僵局。
一个更好的选择是肯定和分配线程的优先级,以便于阻塞一个(或几个)线程,其他的线程继续获取他们所需的锁,就像没有死锁发生同样。若是分配给线程的优先级是固定的,相同的线程将老是被富裕更高的优先级。为了不这种状况,能够在检测死锁时随机分配优先级。
原文连接: