死锁
死锁实例
当一个线程永远地持有一个锁,并且其他的线程去尝试获得这个锁,那么他们将永远的被阻塞,如果线程A持有锁L并且想获得锁R,线程B持有锁R并且想获得锁L,那么这两个线程将永远的等待下去,这是死锁的最简单形式。
下面给出生产死锁的简单代码实例:
public class DeadLock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
synchronized (left) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (right) {
System.out.println("leftRight end");
}
}
}
public void rightLeft() {
synchronized (right) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (left) {
System.out.println("rightLeft end");
}
}
}
public static void main(String[] args) {
DeadLock dLock = new DeadLock();
new Thread(new Runnable() {
@Override
public void run() {
dLock.leftRight();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
dLock.rightLeft();
}
}).start();
}
}
没有如何结果输出,如下图所示。

原因是产生了死锁。下面分析下死锁的定位。
-
jps获取当前Java虚拟机进程的pid。
jps命令获取pid
-
jstack打印堆栈信息。jstack打印的最后发现了一个死锁,但是我们关注线程的信息。
jstack输出结果
介绍一下输入信息的意思,以Thread-1为例:
(1)“Thread-1”表示线程名字
(2)“prio=5”表示线程优先级
(3)“tid=0x00007fbb02844000”表示线程Id
(4)“nid=0x4f03”表示本地线程Id,Java线程依附于本地线程来运行,实际上本地线程在执行Java线程代码,本地线程才是真正的线程实体。在Linux环境中可以使用top -H -P 进程Id来查看本地线程信息,注意本地线程十进制表示,nid是十六进制,需要转化一下 0x4f03对应的十进制进程Id为20227。
(5)“[0x000070000c4eb000]”表示线程占用的内存地址。
(6)“java.lang.Thread.State: BLOCKED”表示线程的状态。
现在可以知道thread-1和thread-0处于BLOCKED状态。分析两个线程:
(1)thread-1获得了锁0x00000007956e5c68,在等待锁0x00000007956e5c58。
(2)thread-0获得了锁0x00000007956e5c58,在等待锁0x00000007956e5c68。
两个线程都在等待对方持有的锁,所以就会永远等待下去。
避免死锁的方法
- 加锁顺序。所有的锁需按照顺序获得。前面的锁获得后才能获得后面的锁
- 加锁时限。超过时限之后,释放所有的锁,过段时间重新申请锁资源。
- 死锁检测。当检测出发生死锁之后:
(1)一个可行的方案是释放所有的锁,回退,等待随机时间重试。但是有大量的线程竞争同一把锁,他们还是会发生死锁。
(2)更好的方案是设置优先级,让一个或者几个线程回退,剩下的线程继续申请锁。
网友评论