一、死锁定义
通过锁机制解决了线程同步问题,但同时带来了一个新问题——死锁。
死锁是指多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致线程循环等待进程停止的问题。当某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”问题。
死锁图示:
二、死锁代码示例
// 死锁示例
public class ThreadDemo3 implements Runnable {
int type;
C1 c1;
C2 c2;
ThreadDemo3(int type, C1 c1, C2 c2) {
this.type = type;
this.c1 = c1;
this.c2 = c2;
}
public void run() {
if(type == 1) {
// 占有c1
synchronized(c1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
// 已经占有c1,未释放c1,想申请c2
synchronized(c2) {
System.out.println(Thread.currentThread().getName() + " get c2");
}
}
}
if(type == 2) {
// 占有c2
synchronized(c2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
// 已经占有c2,未释放c1,想申请c1
synchronized(c1) {
System.out.println(Thread.currentThread().getName() + " get c1");
}
}
}
}
}
// 共享资源1
class C1 { }
// 共享资源2
class C2 { }
执行:
public static void main(String[] args) {
C1 c1 = new C1();
C2 c2 = new C2();
ThreadDemo3 t1 = new ThreadDemo3(1, c1, c2);
ThreadDemo3 t2 = new ThreadDemo3(2, c1, c2);
new Thread(t1).start();
new Thread(t2).start();
}
输出:
可以看到,程序会一直停留,t1会一直获取不到c2,t2也一直获取不到c1。
三、死锁条件及解决方案
如果系统中以下四个条件同时成立,那么就能引起死锁:
- 互斥:资源必须处于非共享模式,即一次只有一个线程可以使用。如果另一线程申请该资源,那么必须等待直到该资源被释放为止。
- 占有并等待:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他线程所占有。
- 非抢占:资源不能被抢占。只能在持有资源的线程完成任务后,该资源才会被释放。
- 循环等待:有一组等待线程
{P0, P1,..., Pn}
,P0
等待的资源被P1
占有,P1
等待的资源被P2
占有,…,Pn-1
等待的资源被Pn
占有,Pn
等待的资源被P0
占有。
我们只要破坏上面任意一个或多个条件,就能破坏死锁。
将上面的示例代码run()方法修改,t1先释放c1后,再去申请c2;或者t2先释放c2后,再去申请c1。都能破坏死锁。
public void run() {
if(type == 1) {
// 占有c1
synchronized(c1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
// 先释放c1,再申请c2
synchronized(c2) {
System.out.println(Thread.currentThread().getName() + " get c2");
}
}
if(type == 2) {
// 占有c2
synchronized(c2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
// 已经占有c2,未释放c1,想申请c1
synchronized(c1) {
System.out.println(Thread.currentThread().getName() + " get c1");
}
}
}
}
输出:
可以看到,死锁已经解开。t1获取到了c2,t2获取到了c1。