在某些情况下,避免死锁是很重要的,下面我就要描述一下避免死锁的三种方法
- 定义锁顺序
- 设置时限
- 死锁检测
定义锁的访问排序
当多个线程同时需要一个锁,而获取这个锁的顺序不一样的时候,死锁就出现了。
那么只要保证所有的锁都被同一种顺序获取的话,那么死锁就不会发生了.
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
如果像Thread3一样,需要多个锁,它必须按照规定的顺序来获取他们.
举个栗子,Thread2 和 Thread3 都不能获取lock C 除非他们已经获取了lock A,那么只要Thread1 一直持有 lockA,那么Thread2 和 Thread 3 都必须等待,直到 Thread 1将lockA释放。然后 Thread 2 和 Thread3必须首先获取 lockA,之后他们才能尝试获取lockB 或者 lockC
总上所述,定义锁的访问排序可以简单有效的避免死锁,但是 这种机制必须建立在你已经了解了那些锁必须在那些锁之前调用,但是事实并非如此,
设置锁时限
另一种避免死锁的机制是去在锁上设置一个定时,也就是说,一个线程只会在一段时间内去获取某一个锁,超过这段时间 ,就放弃。如果一个线程没有在给定时间内成功获取所有的锁,他就会放弃,并且释放所有已经获取的锁。然后等待一个任意的时间再次尝试。当然其他的线程也会给定一个任意的时间去获取相同的所有的locks。通过这样让应用程序继续运行下去
下面给出了一个例子,两个线程以不同的顺序去获得两个锁
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 1 将会尝试重新获取锁在257毫秒之后,但是在这之前Thread 2很可能已经成功获取到了两个锁.(除非有其他的线程在Thead2 等待期间获取到了锁,导致Thread 2 无法获取到全部的锁)
这里需要记住的一个问题是,这里Thread 会释放所有的锁并不是因为遇到了死锁,而是到了时限,它也有可能是因为某个线程长时间持有锁而没有获取到规定内所有的锁(完成一个需要太长时间的任务),还有可能是因为某个其他的线程获取到了当前线程所需要的锁,而导致超时。
此外如果有足够多的线程来竞争相同的资源,这些线程依然冒着风险在同一时间一次又一次的获取执行权 即使到了时限放弃了所有锁,在他们再次等待一个任意时间(0~500)他们仍然有可能发生冲突,也就是说,线程数越多,发生冲突的可能性就越大
设置锁超时机制的一个问题就是不可能对所有java内的同步代码块设置时限,你将不得不去创建一个自定义锁,或者使用java5.0之后的同步并发类。
译者注:貌似在java中可以使用trylock方法来设置锁时限
死锁检测
锁检测是一种十分重要的死锁检测机制,主要应用与锁顺序和锁时限不适用的情况下。
每一次线程持有锁的时候,这种机制都会在一个数据结构(map,graph,etc)中记录一个数据,另外,无论线程当前是否在请求锁,它也会在当前数据结构中记录。当Thread 去请求锁但是被拒绝的时候,这个线程就会遍历整个图去查找死锁。举个栗子,Thread A 请求 lock7 但是lock7正在被ThreadB 持有,然后Thread A 检查Thread B是否正在请求 Thread A持有的某个锁。如果Thread B就是这么做的,那么就代表死锁发生(Thread A 持有 lock1 请求lock7,Thread B 持有 lock7 请求lock1)
当然,一个死锁场景可能是多个线程(两个以上)互相持有各自的锁,Thread A可能在等待Thread B Thread B可能在等待Thread C,Thread C 可能在等待 Thread D 然后 Thread D 在 等待 Thread A,Thread A想要检测死锁的话,他必须传递性的检测所有Thread B 的请求锁,从Thread B 的请求锁来看,Thread A会检测Thread C的请求锁…一直到Thread D 发现 D 的请求锁正在被Thread A 持有,这才能判断死锁情况的发生。
那么将死锁检测出之后会如何?
一个可能的行动就是释放所有持有锁,备份,等待一个随机的时间然后重新请求,这个有些像锁时限机制,只不过这种机制只有当实际检测到了死锁的时候才会备份,不会仅仅是因为超时就释放所有的锁,但是,如果许多线程同时竞争相同的锁,那么就有可能始终以死锁为结局,即使它们备份等待
一个更好的选择就是去设置线程的优先级,那么当一个(一些线程)备份的时候,剩下的线程只要没发生死锁就去继续去获取它们所需要的锁,但是如果线程被设置的优先级是固定的,那么一些线程会始终拥有较高的优先级,为了避免这种情况,在检测到死锁的情况下必须设置随机的优先级。
原文地址:http://tutorials.jenkov.com/java-concurrency/deadlock-prevention.html#detection