⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/xiewenfeng520/article/details/107230996 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

只对死锁代码感兴趣的可以直接跳到第三小节 必然死锁示例,如果对死锁还不太了解的,我们可以一起来讨论以下几个议题

  • 什么是死锁?
  • 死锁有什么危害和特点?
  • 代码实现一个必然死锁的示例
  • 分析死锁的过程

项目环境

  • jdk 1.8

  • github 地址:https://github.com/huajiexiewenfeng/java-concurrent

    • 本章模块:deadlock

1.什么是死锁?

关键词:并发场景,多线程

首先我们需要知道,死锁一定发生在并发场景中。我们为了保证线程安全,有时会给程序使用各种能保证并发安全的工具,尤其是锁,但是如果在使用过程中处理不得当,就有可能会导致发生死锁的情况。

关键词:互不相让

死锁是一种状态,当两个(或多个)线程(或进程)相互持有对方所需要的资源,却又都不主动释放自己手中所持有的资源,导致大家都获取不到自己想要的资源,所有相关的线程(或进程)都无法继续往下执行,在未改变这种状态之前都不能向前推进,我们就把这种状态称为死锁状态,认为它们发生了死锁。

简而言之,死锁就是两个或多个线程(或进程)被无限期地阻塞,相互等待对方手中资源的一种状态。

两个线程死锁的情况

如图所示,线程1 已经持有了 锁1,同时 线程2 也已经持有了锁2,然后 线程1 尝试获取 锁2,但是 线程2 并没有释放 锁2,所以 线程1 处于阻塞状态,同理可知,图中的 线程2 获取 锁1也会被阻塞。

这样一来,线程1 和 线程2 就发生了死锁,因为它们都相互持有对方想要的资源,却又不释放自己手中的资源,形成相互等待,而且会一直等待下去。

2.死锁的影响和危害

2.1 死锁的影响

死锁的影响在不同系统中是不一样的,影响的大小一部分取决于当前这个系统或者环境对死锁的处理能力。

2.1.1 数据库中

例如,在数据库系统软件的设计中,考虑了监测死锁以及从死锁中恢复的情况。在执行一个事务的时候可能需要获取多把锁,并一直持有这些锁直到事务完成。在某个事务中持有的锁可能在其他事务中也需要,因此在两个事务之间有可能发生死锁的情况,一旦发生了死锁,如果没有外部干涉,那么两个事务就会永远的等待下去。

但数据库系统不会放任这种情况发生,当数据库检测到这一组事务发生了死锁时,根据策略的不同,可能会选择放弃某一个事务,被放弃的事务就会释放掉它所持有的锁,从而使其他的事务继续顺利进行。

此时程序可以重新执行被强行终止的事务,而这个事务现在就可以顺利执行了,因为所有跟它竞争资源的事务都已经在刚才执行完毕,并且释放资源了。

2.1.2 JVM 中

在 JVM 中,对于死锁的处理能力就不如数据库那么强大了。如果在 JVM 中发生了死锁,JVM 并不会自动进行处理,所以一旦死锁发生,就会陷入无穷的等待。

2.2 死锁的危害以及特点

关键词:概率性事件

死锁的问题和其他的并发安全问题一样,是概率性的,也就是说,即使存在发生死锁的可能性,也并不是 100% 会发生的。如果每个锁的持有时间很短,那么发生冲突的概率就很低,所以死锁发生的概率也很低。但是在线上系统里,可能每天有几千万次的“获取锁”、“释放锁”操作,在巨量的次数面前,整个系统发生问题的几率就会被放大,只要有某几次操作是有风险的,就可能会导致死锁的发生。

也正是因为死锁“不一定会发生”的特点,导致提前找出死锁成为了一个难题。压力测试虽然可以检测出一部分可能发生死锁的情况,但是并不足以完全模拟真实、长期运行的场景,因此没有办法把所有潜在可能发生死锁的代码都找出来。

关键词:危害大,发生几率不高

一旦发生了死锁,根据发生死锁的线程的职责不同,就可能会造成 子系统崩溃、性能降低 甚至 整个系统崩溃 等各种不良后果。而且死锁往往发生在高并发、高负载的情况下,因为可能会直接影响到很多用户,造成一系列的问题。以上就是死锁发生几率不高但是危害大的特点。

3.必然死锁示例

public class MustDeadLockDemo {

public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadLockTask(lock1, lock2, true), "线程1").start();
new Thread(new DeadLockTask(lock1, lock2, false), "线程2").start();

}

static class DeadLockTask implements Runnable {

private boolean flag;
private Object lock1;
private Object lock2;

public DeadLockTask(Object lock1, Object lock2, boolean flag) {
this.lock1 = lock1;
this.lock2 = lock2;
this.flag = flag;
}

@Override
public void run() {
if (flag) {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "->拿到锁1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->等待锁2释放...");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "->拿到锁2");
}
}
}
if (!flag) {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "->拿到锁2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->等待锁1释放...");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "->拿到锁1");
}
}
}
}
}
}

执行结果:

可以看到程序一直处于阻塞状态。

4.过程分析

其实上面的代码示例发生死锁的过程就是第一小节中 两个线程发生死锁 的情况,这里我们把图拿过来,方便分析。

本文使用 IDEA 进行调试,将断点打在 33 行,run方法的第一行,选择 Thread 模式。

注意:调试过程,因为有人为的等待时间,所以并不会发生死锁,这里只是演示线程执行的顺序和状态。

第一步,线程1进入,flag = true,进入第一个 synchronized 同步块,拿到 lock1(锁1)

第二步,直接点击 Resume Program(F9),进入线程2,此时 flag = false,进入第二个 synchronized 同步块

当然如果 Thread.sleep 的时间够长,或者操作速度够快的话,也能发生死锁。

5.总结

本章我们讨论了什么是死锁,以及死锁的影响和危害,演示了一个必然死锁的例子,然后使用 IDEA 工具调试了两个线程发生死锁的步骤。

在 JVM 中如果发生死锁,可能会导致程序部分甚至全部无法继续向下执行的情况,所以死锁在 JVM 中所带来的危害和影响是比较大的,我们需要尽量避免。

最后如果在面试中碰到这一题,希望大家都能顺利通过。

文章目录
  1. 1. 前言
  2. 2. 项目环境
  3. 3. 1.什么是死锁?
  4. 4. 2.死锁的影响和危害
    1. 4.1. 2.1 死锁的影响
      1. 4.1.0.1. 2.1.1 数据库中
      2. 4.1.0.2. 2.1.2 JVM 中
  5. 4.2. 2.2 死锁的危害以及特点
  • 5. 3.必然死锁示例
  • 6. 4.过程分析
  • 7. 5.总结