Java中的死锁示例

时间:2020-02-23 14:36:12  来源:igfitidea点击:

Java中的死锁是一种编程情况,其中两个或者更多线程被永久阻止。
Java死锁情况发生在至少两个线程和两个或者更多资源的情况下。
其中我编写了一个简单的程序,它将导致java死锁的情况,然后我们将看到如何对其进行分析。

Java中的死锁

让我们看一个简单的程序,我将在Java线程中创建死锁。

package com.theitroad.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 + " acquiring lock on "+obj1);
      synchronized (obj1) {
       System.out.println(name + " acquired lock on "+obj1);
       work();
       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(30000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
}

在上面的程序中,SyncThread实现了Runnable接口,并且通过使用同步块逐个获取每个对象上的锁,从而对两个对象进行工作。

在main方法中,我为SyncThread运行了三个线程,并且每个线程之间都有一个共享资源。
线程以这样的方式运行:它可以获取第一个对象的锁,但是当它试图获取第二个对象的锁时,它将进入等待状态,因为它已经被另一个线程锁定了。
这对导致死锁的线程之间的资源形成了循环依赖性。

当我执行上面的程序时,这里是生成的输出,但是由于Java线程中的死锁,程序永远不会终止。

t1 acquiring lock on java.lang.Object@6d9dd520
t1 acquired lock on java.lang.Object@6d9dd520
t2 acquiring lock on java.lang.Object@22aed3a5
t2 acquired lock on java.lang.Object@22aed3a5
t3 acquiring lock on java.lang.Object@218c2661
t3 acquired lock on java.lang.Object@218c2661
t1 acquiring lock on java.lang.Object@22aed3a5
t2 acquiring lock on java.lang.Object@218c2661
t3 acquiring lock on java.lang.Object@6d9dd520

其中我们可以从输出中清楚地确定死锁情况,但是在实际应用中,很难找到死锁情况并对其进行调试。

如何在Java中检测死锁

为了检测Java中的死锁,我们需要查看应用程序的Java线程转储,在上一篇文章中,我解释了如何使用VisualVM Profiler或者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.theitroad.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.theitroad.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.theitroad.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.theitroad.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.theitroad.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.theitroad.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()方法的另一种实现,并且程序成功运行且没有死锁情况。

  • 仅锁定所需条件:您应该仅对必须处理的资源获得锁定,例如,在上述程序中,我正在锁定完整的Object资源,但是如果我们仅对其中一个字段感兴趣,则应仅锁定具体字段不完整的对象。

  • 避免无限期地等待:如果两个线程正在使用线程连接无限期地等待彼此完成,则可能会陷入死锁。
    如果您的线程必须等待另一个线程完成,则最好始终使用join并尽可能多地等待线程完成。