Java死锁问题排查解决

死锁

死锁,简单来说就是两个或者两个以上的线程在执行的过程中,争夺同一个共享资源造成的相互等待的现象。
如果没有外部干预,线程会一直阻塞无法往下执行,这些一直处于相互等待资源的线程就称为死锁线程。
两个或以上的线程在等待对方执行完毕后才能继续往下执行的时候就发生了死锁。结果就是所以进程无线等待中。
避免死锁的方式:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    Java中的死锁是一个常见的问题,它发生在两个或多个线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干预,这些线程都将无法向前推进。
    分析死锁原因
    线程间以不同的顺序获取多个锁。
    持有锁的线程在尝试获取其他锁之前不释放已持有的锁。

java死锁如何排查

jconsole: Java 自带的监控和管理工具,可以用来检测死锁和线程信息。
jstack: 这是一个命令行工具,可以生成 Java 虚拟机当前时刻的线程快照,用于定位线程死锁。
VisualVM: 一个功能强大的多合一故障诊断和性能分析工具,它也可以用来检测死锁。
JConsole 和 JVisualVM 是 JDK 提供的监控工具,可以用于实时监控线程状态和锁情况。

使用 jstack 排查死锁

假设你使用 jstack 来排查死锁,可以按照以下步骤操作:
找到Java进程ID:使用 jps 或 ps -ef | grep java 命令找到Java进程的ID。
生成线程快照:执行 jstack [pid] 命令,其中 [pid] 是你的Java进程ID。
分析输出:jstack 的输出会包含当前所有线程的堆栈跟踪信息。如果存在死锁,jstack 会在输出的末尾部分显示死锁相关的信息,包括哪些线程涉及以及它们正在等待哪些锁。
使用线程转储(Thread Dump)线程转储 是诊断 Java 死锁的常用方法,可以查看所有线程的状态及其持有的锁信息。获取线程转储的方法:
使用 jps -l 查看当前的java进程的pid,通过包路径很容易区分出自己开发的程序进程。
使用 jstack -l 908 如果出现一下错误信息,说明是死锁线程
使用 JStack:在命令行中,可以使用 jstack 工具生成线程转储。
jstack 其中 是 Java 进程的进程 ID。
分析线程转储:
查找线程状态为 BLOCKED 的线程,这些线程正在等待某些资源。
查看这些线程持有和等待的锁信息,寻找循环等待的情况。
示例线程转储:

“Thread-1” #15 prio=5 os_prio=0 tid=0x00007f3e1b8b0000 nid=0x2903 waiting for monitor entry [0x00007f3e1bca8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.DeadlockExample.method1(DeadlockExample.java:20)
- waiting to lock <0x00000000b20748a0> (a java.lang.Object)
- locked <0x00000000b20748b0> (a java.lang.Object)
at com.example.DeadlockExample.run(DeadlockExample.java:35)

“Thread-2” #16 prio=5 os_prio=0 tid=0x00007f3e1b8b1800 nid=0x2904 waiting for monitor entry [0x00007f3e1bda8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.DeadlockExample.method2(DeadlockExample.java:25)
- waiting to lock <0x00000000b20748b0> (a java.lang.Object)
- locked <0x00000000b20748a0> (a java.lang.Object)
at com.example.DeadlockExample.run(DeadlockExample.java:40)

使用Java Visual VM

jvisualvm.exe位于jdk的bin目录中
可以用来捕获线程转储,并通过可视化工具分析死锁。
在window打开 jvisualvm,jvisualvm是一个图形化的监控工具
下载地址:https://visualvm.github.io

  1. 在windows命令窗口,输出 jvisualvm ,会弹出Java VisualVM窗口

java VisualVm远程连接
https://blog.csdn.net/weixin_39403349/article/details/120141531
使用 Java VisualVM(简称 VisualVM)排查线上死锁是一个常见的实践,因为它提供了丰富的图形化界面,便于实时监控和诊断 Java 应用的运行情况。以下是使用 VisualVM 排查线上死锁的步骤:

  1. 安装和启动 VisualVM
    安装:确保你的 JDK 包含 VisualVM。你可以在 $JAVA_HOME/bin 目录下找到 jvisualvm 可执行文件。如果没有安装,可以从 Oracle 或 OpenJDK 网站下载并安装。
    启动:运行命令 jvisualvm 启动 VisualVM。
  2. 连接到远程 JVM
    启动 JMX 远程访问:确保你的 JVM 启用了 JMX 远程管理功能。在启动 JVM 时添加以下参数:
    -Dcom.sun.management.jmxremote
    -Dcom.sun.management.jmxremote.port=9010
    -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false
    这些参数可以根据你的安全需求进行调整,例如启用认证和 SSL。
    添加远程主机:
    打开 VisualVM。
    在左侧导航栏中右键点击 Remote,选择 Add Remote Host。
    输入远程主机的 IP 地址,并点击 OK。
    添加 JMX 连接:
    在刚添加的远程主机下右键点击 Add JMX Connection。
    输入 JMX 端口号(如上例中的 9010),并点击 OK。
    你应该能看到远程 JVM 出现在 VisualVM 的导航树中。
  3. 监控线程并排查死锁
    打开线程视图:
    双击左侧导航树中的目标 JVM。
    选择顶部的 Threads 选项卡。
    监控线程状态:
    你会看到所有线程的列表以及它们的当前状态,包括 RUNNABLE、WAITING、TIMED_WAITING 和 BLOCKED 等。
    特别关注状态为 BLOCKED 的线程。这些线程通常在等待某个锁,可能与死锁相关。
    线程转储(Thread Dump):
    点击右上角的 Thread Dump 按钮生成当前的线程转储。
    线程转储会显示所有线程的栈信息,包括持有和等待的锁。
    分析线程转储:
    查找 BLOCKED 状态的线程,注意这些线程的栈信息和锁的情况。
    查找 Deadlocked 标记的线程。如果 VisualVM 检测到死锁,会在转储中明显标记出来。
    示例:线程转储分析
    假设你生成了以下线程转储:
    “Thread-1” #15 prio=5 os_prio=0 tid=0x00007f3e1b8b0000 nid=0x2903 waiting for monitor entry [0x00007f3e1bca8000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.DeadlockExample.method1(DeadlockExample.java:20)
    - waiting to lock <0x00000000b20748a0> (a java.lang.Object)
    - locked <0x00000000b20748b0> (a java.lang.Object)
    at com.example.DeadlockExample.run(DeadlockExample.java:35)

“Thread-2” #16 prio=5 os_prio=0 tid=0x00007f3e1b8b1800 nid=0x2904 waiting for monitor entry [0x00007f3e1bda8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.DeadlockExample.method2(DeadlockExample.java:25)
- waiting to lock <0x00000000b20748b0> (a java.lang.Object)
- locked <0x00000000b20748a0> (a java.lang.Object)
at com.example.DeadlockExample.run(DeadlockExample.java:40)
在这个示例中:
Thread-1 持有锁 <0x00000000b20748b0>,正在等待锁 <0x00000000b20748a0>。
Thread-2 持有锁 <0x00000000b20748a0>,正在等待锁 <0x00000000b20748b0>。
这显示了典型的死锁场景,其中两个线程相互等待对方持有的锁。
4. 解决死锁
识别和理解代码:通过线程转储中的方法调用栈,找到锁相关的代码。
修改锁的获取顺序:确保所有线程以相同的顺序获取锁,避免循环等待。
使用超时机制:采用 ReentrantLock 的 tryLock 方法来设置锁的获取超时,避免长时间等待。
重构代码:减少锁的使用和嵌套,或者使用其他同步机制(如 ReadWriteLock、Semaphore)。
总结
使用 VisualVM 排查线上死锁主要包括以下步骤:
连接到远程 JVM。
监控线程状态,特别是 BLOCKED 状态的线程。
生成和分析线程转储,查找死锁信息。
通过修改代码和优化锁的使用方式来解决死锁。
VisualVM 提供了直观的图形界面,使得排查和解决线上死锁变得更加高效和便捷。

使用jconsole

jconsole位于jdk的bin目录中
JConsole:通过 JConsole 可以查看线程状态、监控锁的持有和等待情况。
在window打开JConsole,JConsole是一个图形化的监控工具。

  1. 在windows命令窗口,输出JConsole
  2. 选择到线程的tab页上, 查看线程状态
    可以通过jstack命令去导出线程的dump日志, 然后从dump日志里面定位到具体死锁的程序代码
    JConsole远程监控java应用
    https://blog.csdn.net/She_lock/article/details/100101164

解决和避免Java 死锁

事务尽可能小,不要将复杂逻辑放进一个事务里。
涉及多行记录时,约定不同事务以相同顺序访问。
业务中要及时提交或者回滚事务,可减少死锁产生的概率。
表要有合适的索引。
可尝试将隔离级别改为 RC
程序出现死锁,是因为在多线程环境里面两个或两个以上的线程同时满足 互斥条件、请求保持条件、不可抢占条件、循环等待条件。
出现死锁以后,可以通过jstack命令去导出线程的dump日志, 然后从dump日志里面定位到具体死锁的程序代码
通过修改程序代码去破坏这四个条件里面的任意一个,就可以解决死锁问题。 当然,因为互斥条件因为是锁本身的特性,所以不能被破坏。
解决死锁的方法:
避免嵌套锁:减少锁的数量,避免在持有锁的情况下再次获取其他锁。
按照固定顺序获取锁:确保所有线程按照相同的顺序获取锁,从而避免循环等待。
使用超时机制:使用 ReentrantLock 的 tryLock 方法设置超时,以避免长时间等待锁。
示例:使用 ReentrantLock 的超时机制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class TimeoutLockExample {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    public void tryLockWithTimeout() {
        boolean acquiredLock1 = false;
        boolean acquiredLock2 = false;

        try {
            acquiredLock1 = lock1.tryLock(1, TimeUnit.SECONDS);
            if (acquiredLock1) {
                acquiredLock2 = lock2.tryLock(1, TimeUnit.SECONDS);
                if (acquiredLock2) {
                    // 成功获取所有锁
                } else {
                    // 释放已获取的锁
                    lock1.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (acquiredLock2) {
                lock2.unlock();
            }
            if (acquiredLock1) {
                lock1.unlock();
            }
        }
    }
}

2.2 设计改进
优化设计:
减小锁的粒度:减少每个锁保护的代码区域,降低锁的竞争和持有时间。
使用读写锁:对于读多写少的场景,使用 ReadWriteLock 可以提高并发性。
重构代码:重新设计类和方法,减少锁的复杂性,避免长时间持有锁。
示例:使用读写锁:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    public void readMethod() {
        readLock.lock();
        try {
            // 执行读操作
        } finally {
            readLock.unlock();
        }
    }

    public void writeMethod() {
        writeLock.lock();
        try {
            // 执行写操作
        } finally {
            writeLock.unlock();
        }
    }
}
2.3 使用工具类
使用 java.util.concurrent 包中的工具类:
Semaphore:控制对共享资源的访问。
CountDownLatch:允许一个或多个线程等待直到一组操作完成。
CyclicBarrier:使一组线程在执行到达某个屏障点时,能够相互等待。
示例:使用 Semaphore:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private final Semaphore semaphore = new Semaphore(1);

    public void accessResource() {
        try {
            semaphore.acquire();
            // 访问共享资源
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release();
        }
    }
}

排查 Java 死锁:
使用线程转储分析工具(如 JStack、JVisualVM)获取和分析线程状态。
使用监控工具(如 JConsole、JVisualVM)查看实时的线程和锁情况。
解决 Java 死锁:
避免嵌套锁和循环等待。
使用超时机制来防止线程长时间等待。
使用合适的锁工具类和优化锁策略。
设计时考虑降低锁的复杂性和持有时间。
通过这些步骤,可以有效地排查和解决 Java 死锁问题,提高系统的稳定性和性能

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

思静语

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值