死锁是开发中必须要避免的问题,它会导致线程运行不下去,功能出现问题。下面介绍四种定位死锁的方法。
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,在设计程序时要避免双方互相持有对方的锁,只要互相等待对方释放锁,就有可能出现死锁。