死锁
死锁的定义
两个或两个以上的进程因竞争资源,而处于互相等待的状态,如果没有外力作用,他们都将无法推进下去。典型例子就是哲学家进餐问题。
举个例子
上面的内容可能有些抽象,因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:

我们用一段代码来模拟上述过程:
@Slf4j
public class DemoLock {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(() -> {
synchronized (a) {
try {
System.out.println("now threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
});
Thread threadB = new Thread(()->{
synchronized (b) {
try {
System.out.println("now threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
});
threadA.start();
threadB.start();
}
}
程序执行结果如下:

很明显,程序执行停滞了。
死锁发生必须具备的必要条件
互斥条件(某个资源只能有一个线程持有)
请求和保持条件(对自己获得的资源保持不放,又去请求新资源)
不剥夺条件(线程已经持有的资源不能被剥夺,必须自己释放)
环路等待(发生死锁时一定有一个进程请求的资源构成一个环形的链)
避免死锁的方案
1、以特定顺序获得锁,尝试按照锁对象的HashCode值大小的顺序,分别获得两个锁,这样总会以特定顺序获取锁。
2、超时放弃,当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。
3、死锁预防检测的银行家算法(比较复杂不建议使用)
java中死锁的检测的方法和工具
在这里,我将介绍两种死锁检测工具
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
首先,我们通过jps确定当前执行任务的进程号:
-Air:~$ jps
48672 Launcher
48673 DemoLock
42916 JConsole
3415
48778 Jps
22843 Launcher
10366 Launcher
1151 Launcher
可以确定任务进程号是48673,然后执行jstack命令查看当前进程堆栈信息:
Air:~$ jstack -F 48673
Attaching to process ID 48673, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
Deadlock Detection:
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock Monitor@0x00007fd9a70196b8 (Object@0x00000007960e39d0, a java/lang/Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock Monitor@0x00007fd9a701bf48 (Object@0x00000007960e39c0, a java/lang/Object),
which is held by "Thread-0"
Found a total of 1 deadlock.
Thread 5891: (state = BLOCKED)
Thread 15107: (state = BLOCKED)
- com.clubfactory.sc.core.utils.DemoLock.lambda$main$1(java.lang.Object, java.lang.Object) @bci=22, line=33 (Interpreted frame)
- com.clubfactory.sc.core.utils.DemoLock$$Lambda$2.run() @bci=8 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=748 (Interpreted frame)
Thread 14851: (state = BLOCKED)
- com.clubfactory.sc.core.utils.DemoLock.lambda$main$0(java.lang.Object, java.lang.Object) @bci=22, line=18 (Interpreted frame)
- com.clubfactory.sc.core.utils.DemoLock$$Lambda$1.run() @bci=8 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=748 (Interpreted frame)
Thread 13827: (state = BLOCKED)
Thread 12291: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove(long) @bci=59, line=143 (Interpreted frame)
- java.lang.ref.ReferenceQueue.remove() @bci=2, line=164 (Interpreted frame)
- java.lang.ref.Finalizer$FinalizerThread.run() @bci=36, line=209 (Interpreted frame)
Thread 12035: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Interpreted frame)
- java.lang.Object.wait() @bci=2, line=502 (Interpreted frame)
- java.lang.ref.Reference.tryHandlePending(boolean) @bci=54, line=191 (Interpreted frame)
- java.lang.ref.Reference$ReferenceHandler.run() @bci=1, line=153 (Interpreted frame)
可以看到,进程的确存在死锁,两个线程分别在等待对方持有的Object对象
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
我们在命令行中敲入jconsole命令,会自动弹出以下对话框,选择进程48673,并点击“链接”

进入所检测的进程后,选择“线程”选项卡,并点击“检测死锁”

可以看到以下画面:

可以看到进程中存在死锁。
以上例子我都是用synchronized关键词实现的死锁,如果读者用ReentrantLock制造一次死锁,再次使用死锁检测工具,也同样能检测到死锁,不过显示的信息将会更加丰富,有兴趣的读者可以自己尝试一下。

浙公网安备 33010602011771号