一、什么是死锁
锁是个非常有用的工具,运用场景非常多,因为它使用起来非常简单,而且易于理解。但同时它也会带来一些困扰,那就是可能会引起死锁,一旦产生死锁,就会造成系统功能不可用。
比如我们现在有ThreadA和ThreadB两个线程,ThreadA当中有两个锁,ThreadB当中也有两个锁。ThreadA当中的LOCK1锁住了资源A,ThreadB当中的LOCK1锁住了资源B。但是ThreadA当中的LOCK2想要被ThreadB的LOCK1锁锁住的资源B,而ThreadB当中的LOCK2想要ThreadA当中被LOCK1锁住的资源A,而且线程的双发只有等到LCOK2锁住了自己想要的资源才会释放被LCOK1锁住的资源,所以此时就会出现死锁。
总结:死锁就是两个或两个以上的线程去争抢同一个共享资源导致互相等待的一个现象。在没有外部干预的情况下,线程无法继续向下去执行。
二、展示什么是死锁
public class ConcurrencyTest {
public static Object A = new Object();
public static Object B = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (A){
System.out.println(Thread.currentThread().getName()+"获取了资源A的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println(Thread.currentThread().getName()+"获取了资源B的锁");
}
}
}).start();
new Thread(()->{
synchronized (B){
System.out.println(Thread.currentThread().getName()+"获取了资源A的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A){
System.out.println(Thread.currentThread().getName()+"获取了资源B的锁");
}
}
}).start();
}
}
查看死锁的更多信息
①:根据jps查询所有java线程信息
②:使用jstack查看线程信息
③:观察信息,我们发现两个线程都是BLOCKED的状态
在最后边提示我们出现了死锁
三、产生死锁的必要条件
1.互斥使用
共享资源x和y只能被一个线程占用
2.请求和保持条件
线程t1在已经占据共享资源x的情况下,并且在等待共享资源y的时候不释放共享资源x。 这就意味着即使进程无法继续执行,它仍然保持对已获得资源的控制。
3.不可抢占条件
其他线程不能强行去抢占线程t1已经占有的资源。
4.循环等待
线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待 ---- 两个线程相互等待。
四、如何避免产生死锁
线程在产生死锁以后,只能通过外部的干预来解决只能重启获取kill线程等。所以我们在写代码的时候就应该刻意避免产生线程的死锁,也就是避免同时满足四个必要条件。互斥条件是锁本身的特征无法被破坏,那么其他三个条件都可以被破坏。
- 对于请求保持条件,我们可以在第一次执行的时候,一次性申请所有的共享资源。
- 对于不可抢占条件,占用部分资源的线程,在进一步去申请其他资源的时候,如果申请不到啊,就主动释放它占有的资源。
- 对于循环等待条件,可以按照顺序来去申请锁资源,相当于给资源一个编号,按照编号顺序去申请,就可以避免循环等待的问题。
五、总结
出现死锁以后,可以通过jstack命令导出线程的dump日志,然后从dump日志里面定位到具体死锁的程序代码,通过修改程序代码去破坏这四个条件当中的任意一个就可以解决死锁问题。当然因为互斥条件,因为是锁本身的特性,所以不能被破坏。