检测死锁的几个工具:JStack、JConsole、JVisualVM、ThreadMXBean

死锁是开发中必须要避免的问题,它会导致线程运行不下去,功能出现问题。下面介绍四种定位死锁的方法。

1. 死锁例子

首先,先来看一个简单的死锁例子

public class SynchronizedTest {

	public static void main(String[] args) throws InterruptedException {
		DealThread t1 = new DealThread();
		t1.setFlag("a");
		Thread thread1 = new Thread(t1);
		thread1.start();
		
		Thread.sleep(1000);
		
		t1.setFlag("b");
		Thread thread2 = new Thread(t1);
		thread2.start();
	}
	
	static class DealThread implements Runnable{
		public String username;
		public Object lock1 = new Object();
		public Object lock2 = new Object();
		public void setFlag(String username) {
			this.username = username;
		}
		
		@Override
		public void run() {
			if("a".equals(username)) {
				synchronized (lock1) {
					try {
						System.out.println("username= " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized(lock2) {
						System.out.println("按lock1->lock2代码顺序执行了");
					}
				}
			}
			if("b".equals(username)) {
				synchronized (lock2) {
					try {
						System.out.println("username= " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized(lock1) {
						System.out.println("按lock2->lock1代码顺序执行了");
					}
				}
			}
		}
	}
}

2.JStack

JStack是JDK自带的命令行工具,主要用于线程Dump分析。Dump文件是线程的内存镜像,保存的是进程的执行状态信息。它的位置在JAVA_HOME/bin目录下。它不是图形界面,所以不能双击打开。
先打开CMD命令行,执行jps命令,查看Java进程信息,找出要调试的Java进程号,也就是pid,我这里是12980
在这里插入图片描述
执行jstack -l pid,-l参数可以打印出锁的相关信息,如果是真实项目可能会打印出很多信息,我们可以使用 jstack -l pid > D:deal_thread.log命令将所有信息打印到deal_thread.log文件中。
打印结果较多,只截出死锁的相关信息

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000000363d9d8 (object 0x00000000d5cf9808, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000000363d878 (object 0x00000000d5cf9818, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.morlia.platform.synchronizedtest.SynchronizedTest$DealThread.run(SynchronizedTest.java:69)
        - waiting to lock <0x00000000d5cf9808> (a java.lang.Object)
        - locked <0x00000000d5cf9818> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)
"Thread-0":
        at com.morlia.platform.synchronizedtest.SynchronizedTest$DealThread.run(SynchronizedTest.java:55)
        - waiting to lock <0x00000000d5cf9818> (a java.lang.Object)
        - locked <0x00000000d5cf9808> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)

Found 1 deadlock.

此时从打印信息可以看出Thread-0和Thread-1线程在run方法中死锁了。

3.JConsole

JConsole是JDK自带的一个虚拟机监控工具,它的位置在JAVA_HOME/bin目录下,双击打开之后,选择可能死锁的线程,点击连接。
在这里插入图片描述
进入监控页面之后先点击线程,然后点击左下角的检测死锁
在这里插入图片描述
如果发生死锁,在死锁一栏就会出现死锁的线程信息
在这里插入图片描述
此信息和jstack打印出的信息类似

4.JVisualVM

JVisualVM是jdk提供的一个非常强大的排查Java程序问题的工具,可以监控程序性能、查看jvm配置信息、堆快照、线程堆栈信息。算是程序优化的必备工具。工具位于JDK的bin目录中,也是图形界面,可以直接双击打开。
在这里插入图片描述
左边选择你需要看的进程,在右边先点击进程页签,然后点击线程页签,此时我们可以看到有红字提示说检测到死锁,于是,我们点击线程Dump来获取更多信息。
此时它会出现threaddump页面,上面的信息就是dump文件的信息。
在这里插入图片描述

5.ThreadMXBean

有时候,我们希望程序能自己发现死锁,而不是需要我们来寻找。Java就提供了这样的工具类。
样例代码:

public class SynchronizedTest {

	public static void main(String[] args) throws InterruptedException {
		DealThread t1 = new DealThread();
		t1.setFlag("a");
		Thread thread1 = new Thread(t1);
		thread1.start();
		
		Thread.sleep(1000);
		
		t1.setFlag("b");
		Thread thread2 = new Thread(t1);
		thread2.start();
		
		Thread.sleep(4000);
		
		//获取xbean实例
		ThreadMXBean mBean = ManagementFactory.getThreadMXBean();
		//获取死锁的线程ID
		long[] dealThreads = mBean.findDeadlockedThreads();
		//遍历
		for(long pid : dealThreads) {
			//获取线程信息
			ThreadInfo threadInfo = mBean.getThreadInfo(pid);
			System.out.println(threadInfo);
		}
	}
	
	static class DealThread implements Runnable{
		public String username;
		public Object lock1 = new Object();
		public Object lock2 = new Object();
		public void setFlag(String username) {
			this.username = username;
		}
		
		@Override
		public void run() {
			if("a".equals(username)) {
				synchronized (lock1) {
					try {
						System.out.println("username= " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized(lock2) {
						System.out.println("按lock1->lock2代码顺序执行了");
					}
				}
			}
			if("b".equals(username)) {
				synchronized (lock2) {
					try {
						System.out.println("username= " + username);
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized(lock1) {
						System.out.println("按lock2->lock1代码顺序执行了");
					}
				}
			}
		}
	}
}

运行结果

username= a
username= b
"Thread-1" Id=11 BLOCKED on java.lang.Object@15db9742 owned by "Thread-0" Id=10
"Thread-0" Id=10 BLOCKED on java.lang.Object@6d06d69c owned by "Thread-1" Id=11

检测死锁和获取堆栈信息是比较耗费性能的操作,不能频繁去使用。
死锁是程序设计的Bug,在设计程序时要避免双方互相持有对方的锁,只要互相等待对方释放锁,就有可能出现死锁。

猜你喜欢

转载自blog.csdn.net/d303577562/article/details/109225983