首先你要知道,死锁一定发生在并发场景中。我们为了保证线程安全,有时会给程序使用各种能保证并发安全的工具,尤其是锁,但是如果在使用过程中处理不得当,就有可能会导致发生死锁的情况。
死锁是一种状态,当两个(或多个)线程(或进程)相互持有对方所需要的资源,却又都不主动释放自己手中所持有的资源,导致大家都获取不到自己想要的资源,所有相关的线程(或进程)都无法继续往下执行,在未改变这种状态之前都不能向前推进,我们就把这种状态称为死锁状态,认为它们发生了死锁。通俗的讲,死锁就是两个或多个线程(或进程)被无限期地阻塞,相互等待对方手中资源的一种状态。
下面,我们举一个必然会发生死锁的例子:
/**
* 描述: 必定死锁的情况
*/
public class MustDeadLock implements Runnable {
public int flag;
static Object o1 = new Object();
static Object o2 = new Object();
public void run() {
System.out.println("线程"+Thread.currentThread().getName() + "的flag为" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("线程1获得了两把锁");
}
}
}
if (flag == 2) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("线程2获得了两把锁");
}
}
}
}
public static void main(String[] argv) {
MustDeadLock r1 = new MustDeadLock();
MustDeadLock r2 = new MustDeadLock();
r1.flag = 1;
r2.flag = 2;
Thread t1 = new Thread(r1, "t1");
Thread t2 = new Thread(r2, "t2");
t1.start();
t2.start();
}
}
下面我们对上面发生死锁的过程进行分析:
- 当第 1 个线程运行的时候,它会发现自己的 flag 是 1 ,所以它会尝试先获得 o1 这把锁,然后休眠 500 毫秒。
- 在线程 1 启动并休眠的期间,线程 2 同样会启动起来。由于线程 2 的 flag 是 2,所以它会进入到下面 的 if (flag == 2) 对应的代码块中,然后线程 2 首先会去获取 o2 这把锁。也就是说在线程 1 启动并获取到 o1 这把锁之后进行休眠的期间,线程 2 获取到了 o2 这把锁,然后线程 2 也开始 500 毫秒的休眠。
- 当线程 1 的 500 毫秒休眠时间结束后,它将尝试去获取 o2 这把锁,此时 o2 这个锁正被线程 2 持有,所以线程 1 无法获取到的 o2。
- 紧接着线程 2 也会苏醒过来,它将尝试获取 o1 这把锁,此时 o1 已被线程 1 持有。
所以现在的状态是,线程 1 卡在获取 o2 这把锁的位置,而线程 2 卡在获取 o1 这把锁的位置,这样一来线程 1 和线程 2 就形成了相互等待,需要对方持有的资源才能继续执行,从而形成了死锁。在这个例子里,如果线程 2 比线程 1 先启动,情况也是类似的,最终也会形成死锁。这就是一个“必然发生死锁的例子”。