【java】死锁原理分析与检测工具

最近在看了一些关于多线程死锁分析的博客,尤其是 Java多线程之线程转储和分析(jstack)这篇,讲解的最清晰。在死锁的分析方面有了一点自己的经验,下面将它总结记录下来。

目录

1. 死锁简介

2. 死锁举例

3. 死锁分析工具之VisualVM

3.1 VisualVM简介

3.2 VisualVM中插件安装

3.3 线程监视

3.4 线程dump分析

3.5 Dump内容分析


1. 死锁简介

线程从创建并开始运行、到为满足继续运行时的等待或阻塞,最后再到执行完线程中定义的所有任务【run方法中的代码】而结束线程运行,具有不同的状态。

【java】死锁原理分析与检测工具

阻塞和等待的区别是什么?

  • 阻塞状态的线程是在等待一个排它锁,该锁被另一个线程占用,当另一个线程释放该锁后,该线程才能获取到该锁并退出阻塞状态;
  • 等待状态的线程则是等待一段时间,由系统唤醒或者别的线程唤醒,该线程便退出等待状态。

什么是排它锁和共享锁?

  • 排他锁:如果线程1对数据A加上排他锁后,则其他线程不能再对A加任任何类型的*。获准排他锁的线程既能读数据,又能修改数据。
  • 共享锁:如果线程1对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排他锁。获准共享锁的线程只能读数据,不能修改数据。

为什么会发生死锁?

若干线程相互等待释放*,就陷入无限期等待状态,系统就进入死锁,即出现如下图所示的现象:

【java】死锁原理分析与检测工具

2. 死锁举例

这里引用以为大佬博主的实例:

用ReentrantLock写一个死锁,thread-1获取lockA后,等待lockB,thread-2获取lockB后,等待lockA。测试代码如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestDeadLock {

	private final static Lock lockA = new ReentrantLock(true);
	private final static Lock lockB = new ReentrantLock(true);

	public static void main(String[] args) {

		Thread t1 = new Thread(() -> {
			lockA.lock();
			try {
				System.out.println("thread-1 " + " get lockA. try to get lockB");
				lockB.lock();
			} finally {
				lockB.unlock();
				lockA.unlock();
				System.out.println("thread-1 finished.");
			}
		});
		t1.setName("Thread-1");

		Thread t2 = new Thread(() -> {
			lockB.lock();
			try {
				System.out.println("thread-2 " + " get lockB. try to get lockA ");
				lockA.lock();
			} finally {
				lockA.unlock();
				lockB.unlock();
				System.out.println("thread-2 finished.");
			}
		});
		t2.setName("Thread-2");

		t1.start();
		t2.start();
	}
}

运行效果如下图所示:

【java】死锁原理分析与检测工具

从实验结果中看到,线程1和线程2均在等待对方持有的锁释放,而陷入阻塞状态,系统进入死锁状态。

对于这样简单的多线程死锁我们可以自己分析解决死锁,当遇到更复杂的问题时,我们该如何分析并解决死锁呢?

3. 死锁分析工具之VisualVM

3.1 VisualVM简介

VisualVM 是Netbeans的profile子项目,已在JDK6.0中自带,在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面,有一个jvisualvm.exe文件,双击打开,从UI上来看,这个软件是基于NetBeans开发的了。

【java】死锁原理分析与检测工具

VisualVM 提供了一个可视界面,双击启动 jvisualvm.exe,启动起来后和jconsole 一样同样可以选择本地和远程,如果需要监控远程同样需要配置相关参数。VisualVM可以根据需要安装不同的插件,每个插件的关注点都不同,有的主要监控GC,有的主要监控内存,有的监控线程等。

3.2 VisualVM中插件安装

1、从主菜单中选择“工具”>“插件”。
2、在“可用插件”标签中,选中该插件的“安装”复选框。单击“安装”。
3、逐步完成插件安装程序。

 

【java】死锁原理分析与检测工具

这里主要介绍三个笔者使用的插件:Visual GC、Thread Inspector和VisualVM-JConsole:

【java】死锁原理分析与检测工具

 

3.3 线程监视

有了上面的三个插件,我们就可以分析死锁了,打开VisualVM后,接着运行eclipse中的实例,可以看到VisualVM的本地目录下多了一个线程,如下图所示:

【java】死锁原理分析与检测工具

双击该进程,可以看到下图所示的效果:

【java】死锁原理分析与检测工具

点击“监视”选项卡,可以看到下图所示:

【java】死锁原理分析与检测工具

 从上图中,我们可以看到线程的cpu使用情况和gc的回收情况,以及堆的容量和堆的使用大小,装入内存中的类的总数,以及线程情况。

点击“线程”选项卡,如下图所示:

【java】死锁原理分析与检测工具

如上图所示,我们不仅可以看到程序中所有的线程,同时可以非常清除地看到所有线程的状态, 线程Thread-1和线程Thread-2都处于驻留状态,即阻塞状态。

3.4 线程dump分析

如上一节中的图中所示,VisualVM分析出目前程序中存在死锁,我们可以通过点击“线程 Dump”将各个线程运行的详细信息记录下来,进而分析死锁发生的原因,可以看到生成了如下的结果:

2019-09-25 13:40:45
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"RMI TCP Connection(6)-10.21.17.115" #20 daemon prio=5 os_prio=0 tid=0x000000001ccd9800 nid=0x3728 runnable [0x000000001e2be000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
        - locked <0x0000000783e10b38> (a java.io.BufferedInputStream)
        at java.io.FilterInputStream.read(FilterInputStream.java:83)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:550)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5/1445897752.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x0000000783e10cc0> (a java.util.concurrent.ThreadPoolExecutor$Worker)

"JMX server connection timeout 17" #17 daemon prio=5 os_prio=0 tid=0x000000001adf6000 nid=0x3adc in Object.wait() [0x000000001c5ee000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168)
        - locked <0x0000000783e08db0> (a [I)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"RMI Scheduler(0)" #16 daemon prio=5 os_prio=0 tid=0x000000001adf4000 nid=0x3ab8 waiting on condition [0x000000001c4ee000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e18178> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"RMI TCP Accept-0" #14 daemon prio=5 os_prio=0 tid=0x000000001ae55800 nid=0x13c4 runnable [0x000000001c1ee000]
   java.lang.Thread.State: RUNNABLE
        at java.net.DualStackPlainSocketImpl.accept0(Native Method)
        at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
        at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
        at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
        - locked <0x0000000783e09080> (a java.net.SocksSocketImpl)
        at java.net.ServerSocket.implAccept(ServerSocket.java:545)
        at java.net.ServerSocket.accept(ServerSocket.java:513)
        at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(LocalRMIServerSocketFactory.java:52)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:400)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:372)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x0000000002611000 nid=0xeb0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Thread-2" #11 prio=5 os_prio=0 tid=0x000000001af21000 nid=0xbd4 waiting on condition [0x000000001b8ef000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e18550> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.thread.lock.TestDeadLock.lambda$1(TestDeadLock.java:30)
        at com.thread.lock.TestDeadLock$$Lambda$2/1044036744.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x0000000783e09718> (a java.util.concurrent.locks.ReentrantLock$FairSync)

"Thread-1" #10 prio=5 os_prio=0 tid=0x000000001af1d000 nid=0x3d2c waiting on condition [0x000000001b7ef000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e09718> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.thread.lock.TestDeadLock.lambda$0(TestDeadLock.java:17)
        at com.thread.lock.TestDeadLock$$Lambda$1/303563356.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x0000000783e18550> (a java.util.concurrent.locks.ReentrantLock$FairSync)

"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001ac79800 nid=0x89c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001abff800 nid=0x3960 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001abf9000 nid=0x3aec waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x00000000198fc000 nid=0x38a0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x00000000198af800 nid=0x17e0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001abf8800 nid=0x8fc runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001988a800 nid=0x3658 in Object.wait() [0x000000001abef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x0000000783e19020> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

   Locked ownable synchronizers:
        - None

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019869000 nid=0x1814 in Object.wait() [0x000000001aaef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x0000000783e191d8> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x0000000019867800 nid=0x15dc runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002627000 nid=0xfd4 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002628800 nid=0x3fdc runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000262a800 nid=0x3c8c runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000262c800 nid=0x3e24 runnable 

"VM Periodic Task Thread" os_prio=2 tid=0x000000001acb7800 nid=0x24d4 waiting on condition 

JNI global references: 248


Found one Java-level deadlock:
=============================
"Thread-2":
  waiting for ownable synchronizer 0x0000000783e18550, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "Thread-1"
"Thread-1":
  waiting for ownable synchronizer 0x0000000783e09718, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "Thread-2"

Java stack information for the threads listed above:
===================================================
"Thread-2":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e18550> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.thread.lock.TestDeadLock.lambda$1(TestDeadLock.java:30)
        at com.thread.lock.TestDeadLock$$Lambda$2/1044036744.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-1":
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e09718> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.thread.lock.TestDeadLock.lambda$0(TestDeadLock.java:17)
        at com.thread.lock.TestDeadLock$$Lambda$1/303563356.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

3.5 Dump内容分析

这里以线程Thread-2为例:

"Thread-2" #11 prio=5 os_prio=0 tid=0x000000001af21000 nid=0xbd4 waiting on condition [0x000000001b8ef000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000783e18550> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.thread.lock.TestDeadLock.lambda$1(TestDeadLock.java:30)
        at com.thread.lock.TestDeadLock$$Lambda$2/1044036744.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x0000000783e09718> (a java.util.concurrent.locks.ReentrantLock$FairSync)

“Thread-2”,代表线程的名字,如果有daemon,则表示该线程为守护线程,thread-2不是守护线程,所有没有daemon;

prio=5,代表线程的优先级为5【默认值】。

tid=0x000000001e69b800,代表Java的线程Id(线程在当前虚拟机中的唯一标识)。

nid=0x2340,代表线程本地标识,即线程在操作系统中的标识。

waiting on condition:线程DUMP的状态。一般有如下几种状态:

  • Runnable: 该状态表示线程具备所有可运行条件,在运行队列中等待操作系统的调度,或者正在运行。
  • Wait on condition:该状态表示线程在等待某个条件的发生。比如:等待网络读写,线程在 sleep,线程被parking。用ReentrantLock获取锁等待的时候是这个状态,ReentrantLock的condition.await()也是这个状态。
  • Waiting for monitor entry:线程没有获取过锁,在等待获取锁。用synchronized获取锁等待的时候是这个状态。
  • in Object.wait():线程已获取锁,处于运行状态,但又执行了Object.wait()方法将锁释放掉,并仍然等待该锁。

[0x000000001f18e000] :代表当前运行的线程在堆中的地址范围;

java.lang.Thread.State: WAITING (parking) :代表线程状态是WAITING ,它是被parking挂起了。

parking to wait for  <0x000000076b734700> (a java.util.concurrent.locks.ReentrantLock$FairSync) :它被parking挂起,等待获取一个ReentrantLock的公平锁0x000000076b734700

Locked ownable synchronizers: - <0x000000076b734730> (a java.util.concurrent.locks.ReentrantLock$FairSync):代表它已经拥有的锁是一个ReentrantLock的公平锁0x000000076b734730

同理可以看出,thread-1已经拥有了 一个ReentrantLock的公平锁0x000000076b734700,在等待thread-2有的一个ReentrantLock的公平锁0x000000076b734730

Found one Java-level deadlock:

Found one Java-level deadlock:
=============================
"Thread-2":
  waiting for ownable synchronizer 0x0000000783e18550, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "Thread-1"
"Thread-1":
  waiting for ownable synchronizer 0x0000000783e09718, (a java.util.concurrent.locks.ReentrantLock$FairSync),
  which is held by "Thread-2"

线程1在等线程2拥有的锁,线程2在等线程1拥有的锁。

上一篇:java多线程总结-线程池


下一篇:java concurrent并发包使用