本文是Java并发基础实践系列中的一篇,介绍了最简单的死锁场景,并使用jstack产生的thread dump来查找死锁。(2013.12.29最后更新)
1. 死锁
为了能够维护线程的安全性,Java提供的锁机制,但不恰当地使用锁则可能产生死锁。死锁是并发编程中一个无法绕开的问题。只要在一个任务中使用了一个以上的锁,那么就存在死锁的风险。
死锁产生的直接原因非常简单,即两个线程在相互等待对方所执有的锁。
2. 锁顺序死锁
在死锁场景中,最典型的就是锁顺序死锁,代码清单1就是一个很常见的示例。
public class DeadLock {
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight() {
synchronized (leftLock) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (rightLock) {
System.out.println("leftRight");
}
}
}
public void rightLeft() {
synchronized (rightLock) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (leftLock) {
System.out.println("leftRight");
}
}
}
public static void main(String[] args) {
final DeadLock deadLock = new DeadLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
deadLock.leftRight();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
deadLock.rightLeft();
}
});
t1.start();
t2.start();
}
}
3. Thread Dump
JDK提供了一组命令行工具,其中就包括jstack。通过jstack可以获取当前正运行的Java进程的java stack和native stack信息。如果Java进程崩溃了,也可以通过它来获取core file中的java stack和native stack信息,以方便我们定位问题。
为了能够使用jstack去输出目标Java进程的thread dump,首先必须要弄清楚在执行清单1的程序时,该程序的进程号。JDK提供的另一个命令行工具jps可以获取系统中所有Java进程的相关信息。
在命令行窗口中执行命令jps,即可以得到清单2所示的结果
C:\Documents and Settings\Administrator>jps
2848
4552 DeadLock
5256 Jps
其中4552就是在笔者机器上执行程序DeadLock时所生成Java进程的进程号。
然后再执行命令jstack 4552,在笔者的机器上就会得到清单3所示的结果
C:\Documents and Settings\Administrator>jstack 4552
2013-12-29 18:45:41
Full thread dump Java HotSpot(TM) Client VM (23.25-b01 mixed mode, sharing):
"DestroyJavaVM" prio=6 tid=0x00878800 nid=0xd00 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" prio=6 tid=0x02b56c00 nid=0x14ec waiting for monitor entry [0x02fdf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0" prio=6 tid=0x02b55c00 nid=0x354 waiting for monitor entry [0x02f8f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
"Service Thread" daemon prio=6 tid=0x02b34800 nid=0x133c runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread0" daemon prio=10 tid=0x02b13800 nid=0x10fc waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" daemon prio=10 tid=0x02b11c00 nid=0x1424 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=10 tid=0x02b10800 nid=0x1100 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=8 tid=0x02af4c00 nid=0x1238 in Object.wait() [0x02daf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:189)
"Reference Handler" daemon prio=10 tid=0x02af0000 nid=0x12e8 in Object.wait() [0x02d5f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22b60da0> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:503)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
- locked <0x22b60da0> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=10 tid=0x02aee400 nid=0x129c runnable
"VM Periodic Task Thread" prio=10 tid=0x02b48000 nid=0x89c waiting on condition
JNI global references: 117
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x02af4a3c (object 0x22be6598, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x02af310c (object 0x22be65a0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0":
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
Found 1 deadlock.
在上述输出中,我们可以很明确地看到一个死锁
waiting to lock monitor 0x02af4a3c (object 0x22be6598, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x02af310c (object 0x22be65a0, a java.lang.Object),
which is held by "Thread-1"
并且它还标明了程序是在哪个地方时发现了上述死锁
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0":
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
4. 小结
死锁产生的直接原因非常简单,即两个线程在相互等待对方所执有的锁。锁顺序死锁是其中最经典的场景,此外还有动态的锁顺序死锁。虽然表现形式有所不同,但本质上都是两个线程在以不同的顺序来获取相同锁时,发生了死锁问题。
使用thread dump可以帮助我们分析死锁产生的原因。除了直接使用jstack命令来获取thread dump输出以外,JDK还提供了jvisualvm工具,它能以可视化的方式展示Java程序的进程号并导出thread dump。
相关推荐
《JAVA并发编程实践》是Java开发人员深入理解并发编程的一本经典著作,由Doug Lea撰写,本书中文版高清完整,包含丰富的书签,便于读者查阅和学习。这本书旨在帮助开发者掌握在Java平台上进行高效、安全并发编程的...
《Java并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。...通过阅读这本书,你可以深入理解Java并发编程的理论和实践,提升你的编程能力,为构建高并发、高性能的系统打下坚实基础。
根据提供的信息,我们可以推断出该资源主要关注的是“Java并发编程实践”的相关内容,并且它是一本高清晰度的PDF电子书。虽然提供的链接部分似乎只是重复的网站地址,我们仍可以根据标题、描述以及标签来生成相关的...
《Java并发编程实践》是Java开发者深入理解并发编程的重要参考资料,尤其对于想要提升多线程应用设计和性能优化技能的程序员来说,这本书提供了丰富的实践经验和深入的理论知识。以下是根据提供的章节内容概述的一些...
### Java并发编程实践:构建线程安全应用程序 #### 2.1 什么是线程安全性? 在探讨线程安全性的概念时,我们首先要理解为何在多线程环境下,线程安全性至关重要。在一个复杂对象上进行操作时,从操作开始至完成,...
JAVA并发编程实践-构建执行程序块-学习笔记 JAVA并发编程实践是指在JAVA编程语言中,使用多线程、并发编程来实现高效、可靠的程序执行。构建执行程序块是指在并发编程中,使用线程安全的类来管理状态,以确保程序的...
《Java并发编程实践》是一本深入探讨Java平台并发编程的书籍。它不同于广为熟知的经典之作,但同样具有丰富的知识内容,易于理解,适合初学者和有经验的开发者。本书全面覆盖了Java 5.0引入的并发库,这对于理解和...
### Java并发编程实践-电子书-07章:显示锁详解 #### 一、章节概述 本章节聚焦于Java中的显示锁(Explicit Locks),尤其是`ReentrantLock`的使用和高级特性。显示锁提供了比Java自带的`synchronized`关键字更为...
### Java并发编程实践——第5章 数据冲突及诊断工具MTRAT #### 5.1 如何避免数据冲突 在并发编程中,数据冲突是指多个线程对同一资源的访问而导致的问题,这些问题可能会破坏程序的一致性和正确性。为了避免数据...