多线程【死锁】
死锁现象
同步的另一个弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:死锁
。这种情况能避免就避免掉。
死锁示例
//Thread-0
synchronized(obj1){
//thread-0 拿到锁 obj1
synchronized(obj2){
}
}
//Thread-1
synchronized(obj2){
//Thread-1 拿到锁 obj2
synchronized(obj1){
}
}
// 代码说明:Thread-0线程拿到执行时拿到obj1锁,这时CPU切换到Thread-1上执行,Thread-1线程拿到了obj2锁,这时不管那一个线程要继续执行,都需要获取另一个线程所持有的锁的。两个线程就僵持在这里,程序也卡住不动了。
面试中写死锁程序
public class DeadThread {
public static void main(String[] args) {
Test t1 = new Test(true);
Test t2 = new Test(false);
Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t11.start();
t22.start();
}
}
//单独描述锁对象
class MyLock {
public static final MyLock LOCKA = new MyLock();
public static final MyLock LOCKB = new MyLock();
}
class Test implements Runnable {
private boolean flag ;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if(flag) {
synchronized(MyLock.LOCKA) {
System.out.println(Thread.currentThread().getName()+"...if...MyLock.LOCKA");
synchronized(MyLock.LOCKB) {
System.out.println(Thread.currentThread().getName()+"...if...MyLock.LOCKB");
}
}
}
else {
synchronized(MyLock.LOCKB) {
System.out.println(Thread.currentThread().getName()+"...if...MyLock.LOCKB");
synchronized(MyLock.LOCKA) {
System.out.println(Thread.currentThread().getName()+"...if...MyLock.LOCKA");
}
}
}
}
}
两个线程各自得到一个资源,又互相等待对方释放锁资源导致无法向前推进。
死锁产生的必要条件:
- 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有。
避免死锁的方式:
- 设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。
- 让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实。
- 既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后便会返回一个失败信息。