定位Java线程死锁

制造死锁

死锁的原因通常是线程之间持有对方等待获取的锁,线程一直停留在BLOCKED状态。照此思路可以人为制造死锁如下:

package com.jk.iw.deadlock;

/**
 * @Description:
 * @Author alvin
 * @Date 2021/6/15 3:44 PM
 * @Version 1.0
 */
public class DeadLockFBI extends Thread {
    private String la; // 锁a
    private String lb; // 锁b

    public DeadLockFBI(String la, String lb, String threadName) {
        super(threadName);
        this.la = la;
        this.lb = lb;
    }

    @Override
    public void run() {
        synchronized (la) {
            System.out.println("当前线程:" + this.getName() + ", 获得锁:" + la);
            synchronized (lb) {
                System.out.println("当前线程:" + this.getName() + ", 获得锁:" + lb);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String l1 = "lock1";
        String l2 = "lock2";
        Runnable target;
        Thread t1 = new DeadLockFBI(l1, l2, "线程1");
        Thread t2 = new DeadLockFBI(l2, l1, "线程2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

执行后输出:

当前线程:线程2, 获得锁:lock2
当前线程:线程1, 获得锁:lock1

线程1持有锁lock1并等待获取lock2,线程2持有锁lock2并尝试获取lock1,线程1和线程2相互等待造成死锁。

工具分析死锁

查找PID

方式一:jps命令

jps命令详情

$ jps
8776 Jps
380 
7966 Launcher
7967 DeadLockFBI

方式二:ps命令

ps命令详情

$ ps aux | egrep DeadLock
eunmeac           8975   0.0  0.0  4308576    552 s000  U+    5:34PM   0:00.00 egrep DeadLock
eunmeac           7967   0.0  0.1 10078724  21924   ??  S     4:17PM   0:04.33 /Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8  com.iw.deadlock.DeadLockFBI

通过以上任何一种方式可知DeadLockFBI对应的PID为7967。

线程栈分析

可通过jstack或图形化工具jconsole分析, 这里使用jstack分析。

jstack用法:

Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

执行jstack -l 7967,查找线程为BLOCKED状态的线程,摘取关键信息如下:

"线程2" #12 prio=5 os_prio=31 tid=0x00007fb85400e000 nid=0x5703 waiting for monitor entry [0x000070000218b000]
  java.lang.Thread.State: BLOCKED (on object monitor)
   at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
   - waiting to lock <0x000000076ac26670> (a java.lang.String)
   - locked <0x000000076ac266a8> (a java.lang.String)

  Locked ownable synchronizers:
   - None

"线程1" #11 prio=5 os_prio=31 tid=0x00007fb854893800 nid=0xa803 waiting for monitor entry [0x0000700002088000]
  java.lang.Thread.State: BLOCKED (on object monitor)
   at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
   - waiting to lock <0x000000076ac266a8> (a java.lang.String)
   - locked <0x000000076ac26670> (a java.lang.String)

线程2持有锁的id为0x000000076ac266a8,等待获取的锁的id为0x000000076ac26670;线程1持有锁的id为0x000000076ac26670,等待获取的锁的id为0x000000076ac266a8。线程2和线程1彼此等待,产生死锁。

简单的死锁问题,jstack会直接给出分析结果如下:

Found one Java-level deadlock:
=============================
"线程2":
  waiting to lock monitor 0x00007fb8520070a8 (object 0x000000076ac26670, a java.lang.String),
  which is held by "线程1"
"线程1":
  waiting to lock monitor 0x00007fb850821b58 (object 0x000000076ac266a8, a java.lang.String),
  which is held by "线程2"

Java stack information for the threads listed above:
===================================================
"线程2":
	at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
	- waiting to lock <0x000000076ac26670> (a java.lang.String)
	- locked <0x000000076ac266a8> (a java.lang.String)
"线程1":
	at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
	- waiting to lock <0x000000076ac266a8> (a java.lang.String)
	- locked <0x000000076ac26670> (a java.lang.String)

Found 1 deadlock.

复杂的死锁问题可能要仔细分析才能得出结论,但线程栈总体分析过程如下:

  • 查找可能有问题的线程状态,如BLOCKEDWAITING
  • 查看等待的目标。
  • 对比monitor的持有状态。

程序化分析死锁

Java提供了标准管理API,ThreadMXBean::findDeadlockedThreads 方法用来分析定位死锁。

示例代码如下:

public class DeadLockMonitorFBI extends Thread {
    private String la; // 锁a
    private String lb; // 锁b

    public DeadLockMonitorFBI(String la, String lb, String threadName) {
        super(threadName);
        this.la = la;
        this.lb = lb;
    }

    @Override
    public void run() {
        synchronized (la) {
            System.out.println("当前线程:" + this.getName() + ", 获得锁:" + la);
            synchronized (lb) {
                System.out.println("当前线程:" + this.getName() + ", 获得锁:" + lb);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
        ScheduledExecutorService scheduleService = Executors.newScheduledThreadPool(1);
        // 定时扫描死锁 启动后5s以后每隔5s执行一次
        scheduleService.scheduleAtFixedRate(() -> {
            long[] threadIds = tmx.findDeadlockedThreads();
            Optional.ofNullable(threadIds).map(ts -> {
                ThreadInfo[] tinfos = tmx.getThreadInfo(ts);
                Arrays.stream(tinfos).peek(tinfo -> {
                    System.out.println("死锁线程=>" + tinfo.getThreadName());
                }).toArray();
                return ts;
            });
        }, 5, 5, TimeUnit.SECONDS);

        //死锁业务代码
        String l1 = "lock1";
        String l2 = "lock2";
        Runnable target;
        Thread t1 = new DeadLockMonitorFBI(l1, l2, "线程1");
        Thread t2 = new DeadLockMonitorFBI(l2, l1, "线程2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

输出结果如下:

当前线程:线程2, 获得锁:lock2
当前线程:线程1, 获得锁:lock1
死锁线程=>线程2
死锁线程=>线程1
死锁线程=>线程2
死锁线程=>线程1

如何避免死锁

  • 尽量避免同时使用多个锁尤其是嵌套使用锁,及时释放持有的锁。
  • 如果一定要使用多个锁,设计好锁的获取(释放)顺序。
  • 使用带有超时设置的方法,减少出错的概率。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值