Java死锁详解:示例、原因分析与解决方案
在Java中的死锁示例(第1部分,共3部分)
在Java中,死锁是一种编程情况,其中两个或多个线程被永久阻塞。Java死锁情况发生在至少有两个线程和两个或多个资源的情况下。在这里,我编写了一个简单的程序,会导致Java死锁场景,然后我们将看看如何分析它。
Java 中的死锁
让我们来看一个简单的程序,在这个程序中,我将在Java线程中创建死锁。
package com.Olivia.threads;
public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
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(5000);
t2.start();
Thread.sleep(5000);
t3.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private 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 + " 正在获取锁:"+obj1);
synchronized (obj1) {
System.out.println(name + " 已获取锁:"+obj1);
work();
System.out.println(name + " 正在获取锁:"+obj2);
synchronized (obj2) {
System.out.println(name + " 已获取锁:"+obj2);
work();
}
System.out.println(name + " 已释放锁:"+obj2);
}
System.out.println(name + " 已释放锁:"+obj1);
System.out.println(name + " 执行完毕。");
}
private void work() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上述程序中,SyncThread
实现了Runnable
接口,并通过使用synchronized
块按顺序对两个对象的每一个进行加锁来进行操作。在主方法中,我运行了三个SyncThread
的线程,并且每个线程之间有一个共享资源。这些线程按照一种方式运行,可以成功地对第一个对象进行加锁,但当它试图对第二个对象进行加锁时,它会进入等待状态,因为它已被另一个线程锁定。这导致线程之间的资源形成了循环依赖,导致死锁。当我执行上述程序时,会生成以下输出,但程序永远不会终止,因为Java线程发生了死锁。
t1 正在获取锁:java.lang.Object@6d9dd520
t1 已获取锁:java.lang.Object@6d9dd520
t2 正在获取锁:java.lang.Object@22aed3a5
t2 已获取锁:java.lang.Object@22aed3a5
t3 正在获取锁:java.lang.Object@218c2661
t3 已获取锁:java.lang.Object@218c2661
t1 正在获取锁:java.lang.Object@22aed3a5
t2 正在获取锁:java.lang.Object@218c2661
t3 正在获取锁:java.lang.Object@6d9dd520
在这里,我们可以清楚地从输出中确定死锁的情况,但在实际应用中,找到死锁的情况并对其进行调试是非常困难的。
如何在Java中检测死锁
要在Java中检测死锁,我们需要查看应用程序的Java线程转储。在上一篇文章中,我解释了如何使用VisualVM分析器或使用jstack工具生成线程转储。以下是上述程序的线程转储。
2012-12-27 19:08:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode):
"Attach Listener" daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f658> (a java.lang.Object)
- locked <0x000000013df2f678> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f678> (a java.lang.Object)
- locked <0x000000013df2f668> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f668> (a java.lang.Object)
- locked <0x000000013df2f658> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"Service Thread" daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000013de75320> (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 <0x000000013de75320> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable
"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable
"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable
"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable
"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable
"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable
"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable
"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable
"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable
"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition
JNI global references: 114
Found one Java-level deadlock:
=============================
"t3":
waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object),
which is held by "t2"
"t2":
waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object),
which is held by "t3"
Java stack information for the threads listed above:
===================================================
"t3":
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f658> (a java.lang.Object)
- locked <0x000000013df2f678> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t1":
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f668> (a java.lang.Object)
- locked <0x000000013df2f658> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
"t2":
at com.Olivia.threads.SyncThread.run(ThreadDeadlock.java:41)
- waiting to lock <0x000000013df2f678> (a java.lang.Object)
- locked <0x000000013df2f668> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:722)
Found 1 deadlock.
线程转储输出清楚地显示了死锁情况以及导致死锁的线程和资源。为了分析死锁,我们需要寻找状态为BLOCKED的线程,然后查找它正在等待锁定的资源。每个资源都有一个唯一的ID,可以通过它找到哪个线程已经持有了该对象的锁。例如,线程“t3”正在等待锁定0x000000013df2f658,但它已经被线程“t1”锁定。一旦分析了死锁情况并找到了导致死锁的线程,我们需要对代码进行修改以避免死锁情况。
如何在Java中避免死锁
以下是一些指导方针,遵循这些方针可以避免大多数死锁情况。
- 避免嵌套锁:这是死锁最常见的原因,如果你已经持有一个锁,就避免锁定另一个资源。如果你只使用一个对象锁,几乎不可能出现死锁情况。例如,以下是另一个不使用嵌套锁的run()方法实现,程序能够成功运行且不会出现死锁情况。
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(); } System.out.println(name + " released lock on " + obj1); 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 + " finished execution."); }
- 只锁定必要的资源:你应该只对需要操作的资源获取锁,例如在上面的程序中,我锁定了整个Object资源,但如果我们只对其中的一个字段感兴趣,那么我们应该只锁定那个特定字段,而不是整个对象。
- 避免无限期等待:如果两个线程使用线程连接(thread join)无限期地等待对方完成,就可能导致死锁。如果你的线程必须等待另一个线程完成,最好使用带有最大等待时间的join方法。
关于Java线程死锁的内容就介绍到这里。