JavaEE——死锁
前言
死锁是在开发中经常会遇到的一个问题,指的是多个进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象。
1. 死锁的成因
死锁的产生必须要同时满足以下四个条件:
-
互斥条件(Mutual Exclusion)
资源一次只能由一个线程占用,其他线程必须等待该资源释放,不能使用。 -
占有并等待(Hold and Wait)
线程已经持有至少一个资源,并且正在等待获取其他被占用的资源。 -
非抢占条件(No Preemption)
已分配给进程的资源不能被其他线程强行夺取,必须由线程自行释放。 -
循环等待(Circular Wait)
存在一个线程等待的循环链,每个进程都在等待下一个进程所占用的资源。
以下是一个典型的死锁案例:
public static void main(String[] args) {//死锁案例Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock1) {System.out.println("线程1获取到锁1,等待锁2");try {Thread.sleep(100); // 确保线程2能获取到锁2} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("线程1获取到锁2");}}});Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("线程2获取到锁2,等待锁1");try {Thread.sleep(100); // 确保线程1能获取到锁1} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1) {System.out.println("线程2获取到锁1");}}});}
这四个条件同时成立,就会形成死锁,当我们打破其中任意一个条件,死锁就会消失。
2. 解决死锁问题
上述提到的四个条件,前三个条件都不好打破,第四个是我们最容易破坏的条件,所以为了解决死锁问题,我们最常用的方法就是破坏”循环等待“。
为了破坏这个条件,我们使用锁排序,我们可以把所有的锁进行编号,规定线程按照固定的编号顺序来获取锁,这样就可以避免环路的产生。
就如上面展示的死锁代码,我们把获取锁的顺序进行修改,就可以防止死锁的产生:
//死锁案例Object lock1 = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock1) {try {Thread.sleep(100); } catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("线程1获取到锁2");}}});Thread t2 = new Thread(() -> {synchronized (lock1) {try {Thread.sleep(100); } catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("线程2获取到锁1");}}});}
这里统一了线程对锁的获取顺序,组织了等待环路的产生。
总结
本篇文章简单的介绍了我们常常遇到的死锁问题,讲述了死锁的成因及其解决方法,希望通过这篇文章,能够加深你对死锁的理解,能够轻松的发现并应对可能产生的死锁问题。