【Java核心-进阶】线程——排查死锁、避免死锁

 

1. 线程死锁的原因

死锁:两个或多个线程之间,由于互相持有对方需要的锁,而永久出于阻塞的状态。

 

会引起死锁的代码示例:

Java代码

 

  1. public static void main(String[] args) throws InterruptedException {  

  2.   String lockA = "lock-A";  

  3.   String lockB = "lock-B";  

  4.   

  5.   Thread t1 = new Thread(()->run(lockA, lockB), "Sample-Thread-A");  

  6.   Thread t2 = new Thread(()->run(lockB, lockA), "Sample-Thread-B");  

  7.   

  8.   t1.start();  

  9.   t2.start();  

  10.   t1.join();  

  11.   t2.join();  

  12. }  

  13.   

  14. static void run(String name, String firstLock, String secondLock) {  

  15.   String threadName = Thread.currentThread.getName();  

  16.   synchronized (firstLock) {  

  17.     System.out.println(String.format("%s obtained: %s", threadName, firstLock));  

  18.     try {  

  19.       Thread.sleep(1000);  

  20.       synchronized (secondLock) {  

  21.         System.out.println(String.format("%s obtained: %s", threadName, secondLock));  

  22.       }  

  23.     } catch (InterruptedException e) {  

  24.       // 忽略异常  

  25.     }  

  26.   }  

  27. }  

 

2. 查找定位死锁

死锁的排查定位往往非常费时费力,不要对“快速解决方案”抱有幻想!

 

2.1 通过辅助工具定位死锁

jstack、jConsole 等很多工具都可以检查死锁。

在实际应用中,死锁的定位可能会比较复杂。可以通过以下过程排查:

检查区分线程状态

查看等待的目标

对比 Monitor 等持有状态

以上述示例程序为例,程序在运行过程中,我们可以:

 

# 通过命令 “jstack <pid>” 发现死锁信息:

 

# 通过 jConsole 中检测到此死锁:

 

2.2 通过 ThreadMXBean 定位死锁

如果是开发自己的管理工具,也可以在代码中使用 ThreadMXBean 来检测死锁。

注意:对线程进行快照是一个相对重量级的操作,须慎重选择频率和时机!

Java代码

 

  1. ThreadMXBean mBean = ManagementFactory.getThreadMXBean();  

  2. Runnable dlCheck = () -> {  

  3.   long[] threadIds = mBean.findDeadlockedThreads();  

  4.   if (threadIds != null) {  

  5.     ThreadInfo[] threadInfos = mBean.getThreadInfo(threadIds);  

  6.     System.out.println("Detected deadlock threads:");  

  7.     for (ThreadInfo threadInfo : threadInfos) {  

  8.       System.out.println(threadInfo.getThreadName());  

  9.     }  

  10.   }  

  11. };  

  12.   

  13. ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  

  14. scheduler.scheduleAtFixedRate(dlCheck, 5, 10, TimeUnit.SECONDS);  

 

2.3 排查死循环引起的“假死锁”

如果线程进入死循环,它可能被误认为发生了死锁。但是通过前述的方法又发现没有死锁。

这种情况下,死循环线程的 CPU使用率 会飙升。

所以可以查看CPU使用率,找到CPU占用率高的线程,输出线程堆栈信息,排查代码。

以一个Linux环境中的Java程序为例:

a. 通过 top 命令找出CPU占用率较高的进程。

在运维管理合理的实际项目中,我们通常会事先将业务进程的 pid 记录到某个文件,无需额外查找。

配合容器化、微服务的部署,一个容器或一台(虚拟)服务器上不应运行多个业务进程。

而此处 top命令主要是从多个业务进程中找到那个因死循环而CPU占用率超高的进程。

 

此示例中,目标Java进程的进程号为 24310

 

b. 通过 ps 命令找出Java进程中CPU占用率较高的线程。

ps -mp 24310 -o THREAD,tid

找到对应线程的线程号为 24311

 

c. 通过 jstack 输出线程信息,对照上述线程号,找到对应线程堆栈信息,结合代码排查问题。

jstack 24310

jstack 输出的信息中,线程号是以16进制的格式展示的,所以上述线程号 24311 被显示为 0x5ef7

可通过命令 printf "%x\n" 24311 进行转换。

 

 

3. 避免死锁

产生死锁的基本要素:

  • 互斥条件。锁是独占式,同一时刻只能由单个线程持有。

  • 互斥条件(锁)被长期持有。在使用结束之前,线程不会自己释放锁,也不能被其它线程抢占。

  • 循环依赖。两个或多个线程之间形成锁的链条环。

根据死锁的基本要素避免死锁:

  • 尽量避免使用多个锁。嵌套的 synchronized 和 lock 非常容易出问题。

  • 如果必须用多个锁,需仔细设计推演锁的获取顺序。让所有线程按照相同的顺序获取锁,就是一种常见的思路。

  • 使用带超时的方法。可以让程序假定可能无法获得锁,并设定锁获取失败时的退出逻辑。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中可以使用以下方式来排查死锁: 1. 使用 jstack 工具 jstack 工具可以查看 Java 进程的线程状态和调用栈信息,通过分析线程状态和调用栈信息,可以发现是否存在死锁。 使用方式: ``` jstack <pid> ``` 其中,`<pid>` 是 Java 进程的进程号。 2. 使用 jconsole 工具 jconsole 工具可以查看 Java 进程的线程、内存、CPU 等信息,通过分析线程信息,可以发现是否存在死锁。 使用方式: 1. 启动 Java 进程时添加参数: ``` -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false ``` 其中,`<port>` 是 jconsole 连接 Java 进程的端口号。 2. 启动 jconsole 工具,输入连接信息: ``` Remote Process:<hostname>:<port> ``` 其中,`<hostname>` 是 Java 进程所在主机的 IP 地址或者域名,`<port>` 是 Java 进程的端口号。 3. 在 jconsole 工具中选择“线程”选项卡,查看线程状态和调用栈信息,分析是否存在死锁。 3. 使用 VisualVM 工具 VisualVM 工具是一款免费的 Java 监控和分析工具,可以查看 Java 进程的线程、内存、CPU 等信息,通过分析线程信息,可以发现是否存在死锁。 使用方式: 1. 启动 Java 进程时添加参数: ``` -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false ``` 其中,`<port>` 是 VisualVM 连接 Java 进程的端口号。 2. 启动 VisualVM 工具,选择“远程”选项卡,输入连接信息: ``` <hostname>:<port> ``` 其中,`<hostname>` 是 Java 进程所在主机的 IP 地址或者域名,`<port>` 是 Java 进程的端口号。 3. 在 VisualVM 工具中选择“线程”选项卡,查看线程状态和调用栈信息,分析是否存在死锁

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值