实践讲解死锁及死锁检测(图+文+代码)

1 缘起

在学习ArrayBlockingQueue(ABQ)的过程中,
实现队列阻塞相关的功能是通过java.util.concurrent.locks.Condition,
如enqueue的notEmpty和dequeue的notFull均是Condition,
通过await实现等待,这里我由此想到了中断线程的知识,
通过翻阅java.lang.Thread源码发现,Thread.stop、Thread.suspend和Thread.resume均被JDK1.8标记为Deprecated,
源码中给出了说明文档:https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html,
说是多线程间不安全,可能形成死锁,
于是,我就自己通过代码实现了一下死锁,
同时,结合示意图以及时间序列图分析讲解死锁,
并给出了死锁分析工具jconsole的是使用案例,
检测运行的Java服务中是否有死锁,以及出现死锁的线程,
帮助读者轻松应对知识考核与交流。

2 死锁

多个线程间相互申请锁住对方尚未释放的资源,如对象,方法等,
线程间依赖等待对方释放资源,造成的死循环。
死锁图例如下图所示,
图中展示了线程T1和线程T2分别申请对象A和对象B而造成的死锁,
初始化阶段,线程T1有对象A,线程T2持有对象B,
死锁阶段,线程T1在线程T2持有对象B的阶段申请锁住对象B,
线程T2在线程T1持有对象A的阶段申请锁住对象A,
由于线程在各自占有资源的情况下,相互申请对方持有的资源,
双方都不会退出资源申请,形成环形等待,即死锁。

在这里插入图片描述

2.1 Code

package com.monkey.java_study.lock;

import com.monkey.java_study.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 死锁测试.
 *
 * @author xindaqi
 * @since 2022-10-08 10:59
 */
public class DeadLockTest {

    private static final Logger logger = LoggerFactory.getLogger(DeadLockTest.class);

    public static void main(String[] args) {
        UserEntity u1 = new UserEntity("0x001");
        UserEntity u2 = new UserEntity("0x002");
        Thread t1 = new Thread(() -> {
            synchronized (u1) {
                logger.info(">>>>>>>>>>T1 start");
                try {
                    logger.info(">>>>>>>>>>U1:{}", u1);
                    synchronized (u2) {
                        u2.setNickname("u2-edit");
                        logger.info(">>>>>>>>>>U2:{}", u2);
                    }
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (u2) {
                logger.info(">>>>>>>>>>T2 start");
                try {
                    Thread.sleep(2000);
                    logger.info(">>>>>>>>>>U2:{}", u2);
                    synchronized (u1) {
                        u1.setNickname("u1-edit");
                        logger.info(">>>>>>>>>>U1:{}", u1);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

2.2 结果

在这里插入图片描述

3 死锁分析

3.1 时序图解

下面以时序分解的方式分析死锁,过程如下图所示。
约定:线程T1,线程T2,对象A,对象B
线程T1占有对象A的时间段:[t1, t1+4]
线程T2占有对象B的时间段:[t1+1, t1+4]
线程T1在t1+2时刻申请对象B,此时对象B仍被线程T2持有,线程T1若想申请并锁住对象B,需要等到线程T2释放对象B,
线程T2在t1+3时刻申请对象A,此时对象A仍被线程T1持有,线程T2若想申请并锁住对象A,需要等到线程T1释放对象A,
然而在线程各自占有资源期间,申请了对方尚未释放的资源,相互等待,形成等待环,即死锁。
在这里插入图片描述

3.2 jconsole排查死锁

3.2.1 打开jconsole

jconsole是JDK(1.8)自带的工具,安装JDK后可直接使用,控制台输入jconsole即可调用jconsole客户端,
图形界面如下图所示。
由图可知,jconsole监控了运行的Java进程,既可以连接本地Java进行,又可以连接远程的Java进程,
这里选择DeadLockTest测试死锁的Java进程,并连接。
在这里插入图片描述

3.2.2 连接Java进程

本地Java进程没有配置SSL安全连接,因此jconsole连接时会提示非安全连接,
如下图所示,这里选择不安全的连接,连接DeadLockTest进程。
在这里插入图片描述

3.2.3 线程

进入监控页面后,选择线程属性,如下图所示,
下方有检测死锁的按钮,点击即可检测当前Java进程中的死锁。
在这里插入图片描述

3.2.4 死锁

检测死锁后,发现该DeadLockTest进程中存在死锁线程,
如下图所示,发生死锁的线程为Thread-1和Thread-2。
初始化:
线程Thread-1持有对象A(UserEntity@27b4a46d)
线程Thread-2持有对象B(UserEntity@37a0d490)

在这里插入图片描述

  • Thread-2信息
    对于线程Thread-2,如下图所示,
    由状态可知:申请并锁定对象A(UserEntity@27b4a46d)时,
    该对象被线程Thread-1持有;
    由堆栈跟踪可知:线程Thread-2锁定对象B(UserEntity@37a0d490),
    由总阻止数可知:由于申请的资源被其他线程占有,因此,阻止获取对象A(UserEntity@27b4a46d)。
    在这里插入图片描述
  • Thread-1信息
    对于线程Thread-1,如下图所示,
    由状态可知:申请并锁定对象B(UserEntity@37a0d490)时,
    该对象被线程Thread-2持有;
    由堆栈跟踪可知:线程Thread-1锁定对象A(UserEntity@27b4a46d),
    由总阻止数可知:由于申请的资源被其他线程占有,因此,阻止获取对象B(UserEntity@37a0d490)。
    在这里插入图片描述

4 小结

(1)死锁:多个线程间相互申请锁住对方尚未释放的资源,如对象,方法等,线程间依赖等待对方释放资源,造成的死循环。
(2)Thread以标记弃用:Thread.suspend、Thread.resume和Thread.stop,因会造成死锁;
(3)Thread停止推荐使用Thread.interrupt。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值