Java中死锁的定位与修复

简介: 死锁应该可以说是并发编程中比较常见的一种情况,可以说如果程序产生了死锁那将会对程序带来致命的影响;所以排查定位、修复死锁至关重要;我们都知道死锁是由于多个对象或多个线程之间相互需要 对方锁持有的锁而又没有释放对方所持有...

死锁应该可以说是并发编程中比较常见的一种情况,可以说如果程序产生了死锁那将会对程序带来致命的影响;所以排查定位、修复死锁至关重要;

我们都知道死锁是由于多个对象或多个线程之间相互需要 对方锁持有的锁而又没有释放对方所持有的锁,导致双方都永久处于阻塞状态 ;

如上图所示,线程1持有对象1的锁、线程2持有对象2的锁,持此线程1又想去获取对象2对象锁、线程2想获取对象1对象锁,此时由于双方都没有获取到想要的锁,任务没完成所以也没释放锁,导致一直僵持呢,于是阻塞、产生死锁;

死锁检测

需要检测死锁肯定要先有死锁出现,下面的demo模拟了一个死锁的产生;

public class DeadlockDemo extends Thread {
private BaseObj first;
private BaseObj second;

public DeadlockDemo(String name, BaseObj first, BaseObj second) {
    super(name);
    this.first = first;
    this.second = second;
}

public void reentrantLock() throws InterruptedException {
    first.lock();
    System.out.println(String.format("%s 持有:%s 对象锁,等待获取:%s对象锁", this.getName(), first, second));
    second.lock();
    first.unlock();
    second.unlock();
}
@Override
public void run() {
    try {
        reentrantLock();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) throws InterruptedException {
    ObjOne one = new ObjOne();
    ObjTwo two = new ObjTwo();

    DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two);
    DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one);

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();
}
 } class DeadlockDemo extends Thread {
private BaseObj first;
private BaseObj second;

public DeadlockDemo(String name, BaseObj first, BaseObj second) {
    super(name);
    this.first = first;
    this.second = second;
}

public void reentrantLock() throws InterruptedException {
    first.lock();
    System.out.println(String.format("%s 持有:%s 对象锁,等待获取:%s对象锁", this.getName(), first, second));
    second.lock();
    first.unlock();
    second.unlock();
}
@Override
public void run() {
    try {
        reentrantLock();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) throws InterruptedException {
    ObjOne one = new ObjOne();
    ObjTwo two = new ObjTwo();

    DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two);
    DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one);

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();
}
 }

运行上面的demo将看到程序被阻塞了,没法结束运行;只看到如下运行结果:

Thread1 持有:objOne 对象锁,等待获取:objTwo对象锁 Thread2 持有:objTwo 对象锁,等待获取:objOne对象锁

这demo没法结束运行就是由于产生了死锁,两个线程都在相互对待获取对方所持有的对象锁;

这时候要解决问题就需要找出哪里出现了死锁, 通过代码走查通常不容易发现死锁 ,当然我们这程序很容易发现,因为我们刻意产生的死锁;所以就需要工具来检测死锁,这里可用的工具主要有:jconsole、jvisualvm、jstack等,这些工具其实都是jdk自带的,用法都很类似;

这里使用jvisualvm来检测当前的demo程序是否产生了死锁;打开jvisualvm连接到当前的应用程序即可看到程序的监控信息,如内存、CPU、性能、GC等等;打开进入线程的tab项查看程序的线程信息,这里很明显的就看到了提示该程序被检测除了死锁!

点击 线程Dump可以看到线程的堆栈信息,从中可以看到线程的详细信息,并定位死锁;

从上图可以看到线程产生死锁的原因,Thrad2是等待Thread1、Thread1是等待Thread1, 从下图的堆栈信息即可定位死锁产生的位置;

死锁扫描

除了发现程序出现问题后我们去扫描死锁外,我们还可以实时的去扫描程序用于发现程序中是否存在死锁;

JDK提供了MXBean Api可用于扫描程序是否存在死锁,ThreadMXBean提供了findDeadlockedThreads()方法,可以用于找到产生死锁的线程;这里在上面的demo程序中添加一个方法用于扫描死锁,虽然这种方法可以扫描到死锁但是由于每次都对线程打快照对程序性能会有比较大的影响,所以慎用;

public static void scanDeadLock() {
    ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
    Runnable runnable = () -> {
        long[] ids = mxBean.findDeadlockedThreads();
        System.out.println("扫描死锁...");
        if (ids != null) {
            ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids);
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println(threadInfo);
            }
        }
    };

    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory());
    executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS);
} static void scanDeadLock() {
    ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
    Runnable runnable = () -> {
        long[] ids = mxBean.findDeadlockedThreads();
        System.out.println("扫描死锁...");
        if (ids != null) {
            ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids);
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println(threadInfo);
            }
        }
    };

    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory());
    executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS);
}

避免死锁

解决死锁最好的方法就是避免死锁了,比如上面的demo我们可以把直接使用无参数的lock()方法换为使用tryLock方法,tryLock还可以指定获取锁超时时间,到了超时时间还没获得到锁就会放弃获取锁,当然还有其它方法可以避免死锁;

1、避免使用多个锁、长时间持有锁;

2、设计好多个锁的获取顺序

3、使用带超时的获取锁方法

如果对自己未来有想法,想提升自己,你现在在JAVA这条路上挣扎,也想在IT行业拿高薪,可以参加我们免费的公开课试听学习 干货满满的,选择最适合自己的课程学习,技术大牛亲授,课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来。

群号:468947140

进群修改群备注:开发年限-地区-经验
点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=52j2FVO

相关文章
|
1月前
|
监控 安全 Java
【JAVA】uwb工厂人员定位系统:人员轨迹实时定位
Uwb人员精准定位系统需要具备实时性,将定位数据及时反馈给用户,方便用户进行人员管理和监控。
52 4
|
2月前
|
JavaScript 前端开发 数据可视化
JAVA人员定位系统源码,Java高精度定位系统源码
1、对各个地图以及各个区域内的人员信息快速查看,以图表的形式展示,更加清晰。 2、主要包括人员动态数据、各类告警事件、员工巡检状态等。
40 3
|
3月前
|
Java 程序员
Java程序员-你真的了解死锁吗
Java程序员-你真的了解死锁吗
36 0
|
4月前
|
JavaScript 前端开发 安全
【java】工业园区高精准UWB定位系统源码
巡检管理: 1、可查看巡检人员执行任务的轨迹,全面记录巡检时间、地点、耗时、作业顺序等。 2、结合小程序,使巡检人员执行任务更加方便,一键下发和接收巡检任务。 定位系统技术架构:开发语言:JAVA   开发工具:idea 、VS Code   数 据 库:MYSQL  前端框架:Vue   后端框架:Spring boot  技术架构:单体服务 + 硬件(UWB定位基站、卡牌)
38 1
|
3月前
|
Java
【java】修复:java: Error during the transformation of 'com.aicloud.data.util.ExcelUtils'; post-co...
【java】修复:java: Error during the transformation of 'com.aicloud.data.util.ExcelUtils'; post-co...
59 0
|
2月前
|
Java
Java并发编程中的死锁问题及解决方法
【2月更文挑战第9天】在Java并发编程中,死锁是一种常见但又令人头疼的问题。本文将深入探讨死锁产生的原因,以及针对不同情况所提供的解决方法,帮助读者更好地理解和应对死锁。
|
1月前
|
Java
Java并发编程中的死锁问题及解决方法
【2月更文挑战第11天】 在Java并发编程中,死锁是一个常见但又非常棘手的问题。本文将深入探讨死锁的概念、产生原因以及常见的解决方法,帮助读者更好地理解并发编程中的挑战,并提供实用的解决方案。
38 6
|
3月前
|
Java
Java线程面试题:什么是死锁?如何避免?
Java线程面试题:什么是死锁?如何避免?
33 0
|
3月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,10个定位解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
|
4月前
|
Java
Java多线程:什么是死锁(Deadlock)?
Java多线程:什么是死锁(Deadlock)?
60 0