这里模拟的是两个线程共享同两个对象时,嵌套加锁产生死锁的问题,后面借助JDK提供的工具排查出现死锁的线程。
死锁案例
先创建两个类Obj1和Obj2,类体没有任何代码,只是为了创建两个对象而已。当然了,以下两个类不是必须的,也可以用两个字符串代替这两个类,只要能够让这两个线程共享同两个对象就行。例如,线程1和线程2同时共享学生1和学生2这两个对象。
public class Obj1 {
}
public class Obj2 {
}
接下来创建两个线程类,这里用的是实现Runnable接口的方式,也可以使用继承Thread类或实现Callable接口的方式。当这两个线程开始执行后,第一个线程会拿到Obj1对象的锁,由与线程是并行执行的,可能是第一个线程先执行,也可能是第二个线程先执行。为了确保每个线程只能拿到其中一个对象的锁,让这两个线程都睡眠1000毫秒,1000毫秒过去后两个线程各自拿到了其中一个对象的锁,再尝试去拿另一个对象锁的时候就只能相互等待了,如果程序没有停止运行的话会无限期等下去,这也就产生了死锁。
public class FirstThread implements Runnable {
private Obj1 obj1;
private Obj2 obj2;
public FirstThread(Obj1 obj1, Obj2 obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "拿到了:" + obj1 + "的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "拿到了:" + obj2 + "的锁");
}
}
}
}
public class SecondThread implements Runnable {
private Obj1 obj1;
private Obj2 obj2;
public SecondThread(Obj1 obj1, Obj2 obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "拿到了:" + obj2 + "的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "拿到了:" + obj1 + "的锁");
}
}
}
}
在外面提前创建好Obj1和Obj2对象,注意:Obj1和Obj2对象只new一次。然后在创建第一个线程和第二个线程的同时将Obj1和Obj2这两个被共享的对象作为形参传入即可实现共享。为了更直观查看结果可以给这两个线程取个名称,最后启动两个线程。
public class TestDeadlock {
public static void main(String[] args) {
Obj1 obj1 = new Obj1();
Obj2 obj2 = new Obj2();
Thread t1 = new Thread(new FirstThread(obj1,obj2));
Thread t2 = new Thread(new SecondThread(obj1,obj2));
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
看看效果,很明显,两个线程僵持住了。
排查死锁
如果项目比较大,手写的线程比较多,不确定是哪个线程出问题时,可以借助JDK提供的客户端工具来查看。客户端工具在JDK安装目录下的bin文件夹下的jconsole.exe。
这边是在本地测试的,所以选择本地进程,找到自己进程的名称点击连接。