含义:线程间互相等待资源,而又不释放自身占有的资源,造成彼此无限期等待。
本文涉及如下知识点:
- 死锁代码示例
- 产生死锁需要同时满足4个条件
- 解决方案
- 死锁定位工具 jstack
public class Deadlock {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new SelfObject("obj1");
Object obj2 = new SelfObject("obj2");
Object obj3 = new SelfObject("obj3");
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
t1.start();
Thread.sleep(2*1000);
t2.start();
Thread.sleep(2*1000);
t3.start();
}
}
class SyncThread implements Runnable {
private final Object obj1;
private final Object obj2;
public SyncThread(Object o1, Object o2) {
this.obj1 = o1;
this.obj2 = o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on " + obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on " + obj1);
work();
// 30s之后,t1线程想获取obj2,但是ob2已经被t2线程持有了;
// 同理,t2线程想获取obj3,但是ob3已经被t3线程持有了;
// 同理,t3线程想获取obj1,但是ob1已经被t1线程持有了;
// 所以,log中,我们看到t1线程:t1 acquiring lock on obj2,但是一直无法acquired;另外两个线程也是这个问题。
System.out.println(name + " acquiring lock on " + obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on " + obj2);
work();
}
System.out.println(name + " released lock on " + obj2);
}
System.out.println(name + " released lock on " + obj1);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(10 * 1000); // 工作就是让当前线程休眠30s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class SelfObject extends Object {
private String name;
public SelfObject(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
输出Log:
t1 acquiring lock on obj1
t1 acquired lock on obj1
t2 acquiring lock on obj2
t2 acquired lock on obj2
t3 acquiring lock on obj3
t3 acquired lock on obj3
t1 acquiring lock on obj2 //线程1等待资源2
t2 acquiring lock on obj3 //线程2等待资源3
t3 acquiring lock on obj1 //线程3等待资源1
总结:
有3个资源,分别是Object1,Object2,Object3。3个线程起初各拿到一个资源,但是每个线程需要两个资源,才能完成剩下的任务。
即线程1拿到Object1之后,还需要Object2才能干剩下的工作,此时呢,Object2已经被线程2持有,线程2需要的资源3又被线程3持有,线程3需要的资源1又被线程1持有。这就导致一个无限期互相等待的情况,即死锁。
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程请求资源而无法获得该资源时,不释放已经持有的资源
- 不剥夺条件:被一个线程占用的资源,不能被别的线程抢夺
- 循环等待条件:线程之间形成一种首尾相接、循环等待资源的关系
同时满足这4个条件,才会发生死锁。
解决思路就是打破4个条件中的其中一个,即可解决死锁问题。
- 互斥:打破互斥条件,就意味着允许多个线程同时访问某一个资源,但这种方法受制于实际场景,不容易实现;
- 保持:打破保持条件,就意味着一个线程没有请求到资源时,需要先释放手头上的资源,再请求。这种也比较难找到适用场景;
- 不剥夺:线程在运行前,必须请求到所有需要用的资源,才能运行。缺点是,导致资源利用率、并发性降低;
- 循环等待:避免出现资源申请环路,就对资源进行编号,按号分配。这种方式可以提高资源利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。
jstack主要用来查看某个Java进程内的线程堆栈信息。
对上例执行命令:
jstack your_pid // 进程id
结果如下:
Found one Java-level deadlock: =============================
"t3":
waiting to lock monitor 0x00007fef15839e08 (object 0x00000007958cded0, a concurrency.lock.SelfObject),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007fef148089f8 (object 0x00000007958cdf10, a concurrency.lock.SelfObject),
which is held by "t2"
"t2":
waiting to lock monitor 0x00007fef1480b758 (object 0x00000007958cdf50, a concurrency.lock.SelfObject),
which is held by "t3"
Java stack information for the threads listed above: ===================================================
"t3":
at concurrency.lock.SyncThread.run(Deadlock.java:65)
- waiting to lock <0x00000007958cded0> (a concurrency.lock.SelfObject)
- locked <0x00000007958cdf50> (a concurrency.lock.SelfObject)
at java.lang.Thread.run(Thread.java:745)
"t1":
at concurrency.lock.SyncThread.run(Deadlock.java:65)
- waiting to lock <0x00000007958cdf10> (a concurrency.lock.SelfObject)
- locked <0x00000007958cded0> (a concurrency.lock.SelfObject)
at java.lang.Thread.run(Thread.java:745)
"t2":
at concurrency.lock.SyncThread.run(Deadlock.java:65)
- waiting to lock <0x00000007958cdf50> (a concurrency.lock.SelfObject)
- locked <0x00000007958cdf10> (a concurrency.lock.SelfObject)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
可以清楚的看到,线程t1, t2, t3分别被哪些资源held。
获取进程id的方法:
public static void printPID() {
String name = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(name);
// get pid
String pid = name.split("@")[0];
System.out.println("Pid is: "+pid);
}
参考内容: