最近在看了一些关于多线程死锁分析的博客,在死锁的分析方面有了一点自己的经验,下面将它总结记录下来。
3.6 利用Thread Dumps Analysis分析Thread Dump脚本
1. 死锁简介
1.1 死锁的产生
所谓死锁就是进程循环等待它方占有的资源而无限制的僵持下去的局面。
以一个简单的例子来解释:一个桥,最多可以通过一个车子,但是左右两边都来了车子,而且都上了桥,左边的车子,占用了左边的桥资源,二右边的车子,占用了右边的桥的资源。左边的车子等待右边的车子让出右边的资源,但是右边的车子却要左边的车子让出左边的资源。双方都不放弃自己所占有的资源,却都想着让对方让出自己的资源,这就会无限制的等待下去。
上述的车子表示进程,桥面代表着资源,资源只能由一个进程占有,不允许两个进程同时占有,结果两个进程等不能继续执行,如果不采取其他的措施,上述两个进程的等待状况会无限制的持续下去,这就是进程的死锁。产生死锁的根本原因就是资源有限且操作不当。
1.2 线程
线程从创建并开始运行、到不再满足继续运行时的等待或阻塞,最后再到执行完线程中定义的所有任务【run方法中的代码】而结束线程运行,具有不同的状态。
阻塞和等待的区别是什么?
- 阻塞状态的线程是在等待一个排它锁,该锁被另一个线程占用,当另一个线程释放该锁后,该线程才能获取到该锁并退出阻塞状态;
- 等待状态的线程则是等待一段时间,由系统唤醒或者别的线程唤醒,该线程便退出等待状态。
1.3 锁
系统中,我们常常需要多线程的方式操作一个队列,这时候,为了保证所有线程能够按序访问,就需要锁来实现,实现线程同步本质上,线程同步可以理解为线程互斥,即不允许多个线程同时执行某一操作。当一个线程需要对该队列进行增删改操作时,加锁控制这些操作,使得对该队列的操作只有在获得锁的线程在执行完成所有操作之后,才能被另一个线程操作,就能实现线程安全。
什么是排它锁和共享锁?
- 排他锁:如果线程1对数据A加上排他锁后,则其他线程不能再对A加任任何类型的封锁。获准排他锁的线程既能读数据,又能修改数据。
- 共享锁:如果线程1对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排他锁。获准共享锁的线程只能读数据,不能修改数据。
死锁发生的条件是什么?
- 互斥,共享资源只能被一个线程占用
- 占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
- 不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
- 循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源
避免死锁的方法有哪些?
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。对于第一个条件 "互斥" 是不能破坏的,因为加锁就是为了保证互斥。因此,主要破坏另外的三个条件,就可以避免死锁的产生:
- 一次性申请所有的资源,破坏 "占有且等待" 条件
- 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
- 按序申请资源,破坏 "循环等待" 条件
避免死锁的java具体实现请移步至:如何避免死锁
2. 死锁举例
这里引用Java多线程之线程转储和分析(jstack)中的实例。
线程A获取锁A后,请求获取锁B,线程B获取锁B后,请求获取锁A,代码如下:
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();
}
}
运行效果如下图所示:
从实验结果中看到,线程1和线程2均在等待对方持有的锁释放,而陷入阻塞状态,系统进入死锁状态。
对于这样简单的多线程死锁我们可以自己分析解决死锁,当遇到更复杂的问题时,我们该如何分析并解决死锁呢?
3. 死锁分析
3.1 VisualVM简介
VisualVM 是Netbeans的profile子项目,已在JDK6.0中自带,在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面,有一个jvisualvm.exe文件,双击打开,从UI上来看,这个软件是基于NetBeans开发的了。
VisualVM 提供了一个可视界面,双击启动 jvisualvm.exe,启动起来后和jconsole 一样同样可以选择本地和远程,如果需要监控远程同样需要配置相关参数。VisualVM可以根据需要安装不同的插件,每个插件的关注点都不同,有的主要监控GC,有的主要监控内存,有的监控线程等。
3.2 VisualVM中插件安装
1、从主菜单中选择“工具”>“插件”。
2、在“可用插件”标签中,选中该插件的“安装”复选框。单击“安装”。
3、逐步完成插件安装程序。
这里主要介绍三个笔者使用的插件:Visual GC、Thread Inspector和VisualVM-JConsole:
3.3 线程监视
有了上面的三个插件,我们就可以分析死锁了,打开VisualVM后,接着运行eclipse中的实例,可以看到VisualVM的本地目录下多了一个线程,如下图所示:
双击该进程,可以看到下图所示的效果:
点击“监视”选项卡,可以看到下图所示:
从上图中,我们可以看到线程的cpu使用情况和gc的回收情况,以及堆的容量和堆的使用大小,装入内存中的类的总数,以及线程情况。
点击“线程”选项卡,如下图所示:
如上图所示,我们不仅可以看到程序中所有的线程,同时可以非常清除地看到所有线程的状态, 线程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拥有的锁。
3.6 利用Thread Dumps Analysis分析Thread Dump脚本
从前一节可以看到,我们可以通过Thread Dump后的脚本分析死锁,但是,也可以看到,Thread Dump脚本是非常大的,而且我们的举例还只是一个简单的两个线程之间产生了死锁,在多线程场景下,往往会有很多的线程,这时就肯定会导致线程脚本规模很大,人工分析就变得非常困难,因此,这里笔者介绍一下Thread Dumps Analysis工具,看一下如何非常直观地分析死锁。
网上有很多该软件的下载方式,这里不再提供,如果有需要,请留言。
复制Thread Dump产生的脚本,然后打开TDA软件,File->get logfile from clipboard,之后可以看到如下图所示:
共检测到一个死锁,死锁的原因软件也给出了分析。如有疑问,欢迎评论区留言。