死锁是多线程中的常见问题,经常与业务逻辑挂钩。如果逻辑复杂,检查起来会很困难。如果这时候有工具辅助,就方便多了。
这里介绍两个检查死锁的工具,都是jdk自带的,使用起来也非常简单。这两个工具分别是Jconsole和Jstack。
首先来一段死锁代码:
public class TestThreadLock {
public void sync1() {
synchronized(this) {
try {
System.out.println("test1");
Thread.sleep(3000);// 休眠3秒,以让步给线程b的sync2方法,让其获取到类锁
synchronized(TestThreadLock.class) {
System.out.println("test11");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void sync2() {
synchronized(TestThreadLock.class) {
try {
System.out.println("test2");
Thread.sleep(3000); // 休眠3秒,以让步给线程a的sync1方法,让其获取到对象锁
synchronized(this) {
System.out.println("test21");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestThreadLock t = new TestThreadLock();
Thread a = new Thread(new Runnable() {
public void run() {
t.sync1();
}
});
a.setName("Thread-0");
Thread b = new Thread(new Runnable() {
public void run() {
t.sync2();
}
});
b.setName("Thread-1");
a.start();
b.start();
}
}
代码解析:
- 线程a调用sync1方法,会先获取TestThreadLock的对象锁,然后休眠3秒;
- 趁着线程a休眠的时候,线程b运行,调用sync2方法,会获取TestThreadLock的类锁,然后也休眠3秒;
- 线程b休眠,就轮到线程a运行,此时线程a需要获取TestThreadLock的类锁,但是它被线程b占用着,线程a等待锁释放;
- 常规轮换到线程b执行,线程b需要TestThreadLock的对象锁,此时它被线程a占用着,线程b等待;
- 如此往复,线程a、b互相等待对方的锁,而自身占用的锁又必须获取到对方的锁之后才能释放,于是就形成了死锁。
接下来,我们就可以用工具来检查这个死锁。
Jconsole
Jconsole是JDK自带的图形化界面工具,打开cmd,输入jconsole,就可以打开工具了。
1、选择当前进程。
2、在头部tab栏点击“线程”进入线程界面,点击最下方“检测死锁”进行检查。
3、如果检查到死锁,这里就会把死锁的线程都列出来。
如上,可以看出:
1、Thread-1线程在等待Thread.TestThreadLock@517bf122
这个锁,而且可以看出来,这是个类锁;
2、Thread.TestThreadLock@517bf122
这个锁的拥有者是Thread-0;
3、当前Thread-1调用sync2方法,持有的锁是java.lang.Class@6d06d69c
(对象锁)。
注:Jconsole工具除了可以检测死锁,还可以监控当前进程的内存情况、类情况等,是个很简单方便的工具,有兴趣的可以深入了解。
Jstack
Jstack是JDK自带的一种堆栈跟踪工具。
1、打开cmd,目录定位到java安装目录的bin目录下;
2、输入命令:jps
;
3、查找当前进程对应的端口;(其中TestThreadLock
就是当前进程)
4、输入命令:jstack -l [端口号]
;
5、得到当前线程的锁情况。(在打印出来的日志的最后)
如上,可以看到,最后的日志很清楚的展示了当前哪个线程获取了哪个锁,等待着哪个锁。以Thread-1线程为例:
1、Thread-1在等待着锁,且是类锁Thread.TestThreadLock
;
2、Thread-1占用着锁,且是对象锁java.lang.Class for Thread.TestThreadLock
(TestThreadLock对象);
注:检测死锁,只是Jstack功能的一个体现,它主要是监测当前程序的堆栈情况。对于程序的运行监控和问题排查,是很有帮助的。