制造死锁
死锁的原因通常是线程之间持有对方等待获取的锁,线程一直停留在BLOCKED
状态。照此思路可以人为制造死锁如下:
package com.jk.iw.deadlock;
/**
* @Description:
* @Author alvin
* @Date 2021/6/15 3:44 PM
* @Version 1.0
*/
public class DeadLockFBI extends Thread {
private String la; // 锁a
private String lb; // 锁b
public DeadLockFBI(String la, String lb, String threadName) {
super(threadName);
this.la = la;
this.lb = lb;
}
@Override
public void run() {
synchronized (la) {
System.out.println("当前线程:" + this.getName() + ", 获得锁:" + la);
synchronized (lb) {
System.out.println("当前线程:" + this.getName() + ", 获得锁:" + lb);
}
}
}
public static void main(String[] args) throws InterruptedException {
String l1 = "lock1";
String l2 = "lock2";
Runnable target;
Thread t1 = new DeadLockFBI(l1, l2, "线程1");
Thread t2 = new DeadLockFBI(l2, l1, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
执行后输出:
当前线程:线程2, 获得锁:lock2
当前线程:线程1, 获得锁:lock1
线程1持有锁lock1
并等待获取lock2
,线程2持有锁lock2
并尝试获取lock1
,线程1和线程2相互等待造成死锁。
工具分析死锁
查找PID
方式一:jps命令
$ jps
8776 Jps
380
7966 Launcher
7967 DeadLockFBI
方式二:ps命令
$ ps aux | egrep DeadLock
eunmeac 8975 0.0 0.0 4308576 552 s000 U+ 5:34PM 0:00.00 egrep DeadLock
eunmeac 7967 0.0 0.1 10078724 21924 ?? S 4:17PM 0:04.33 /Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 com.iw.deadlock.DeadLockFBI
通过以上任何一种方式可知DeadLockFBI
对应的PID为7967。
线程栈分析
可通过jstack
或图形化工具jconsole
分析, 这里使用jstack
分析。
jstack用法:
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
执行jstack -l 7967
,查找线程为BLOCKED
状态的线程,摘取关键信息如下:
"线程2" #12 prio=5 os_prio=31 tid=0x00007fb85400e000 nid=0x5703 waiting for monitor entry [0x000070000218b000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
- waiting to lock <0x000000076ac26670> (a java.lang.String)
- locked <0x000000076ac266a8> (a java.lang.String)
Locked ownable synchronizers:
- None
"线程1" #11 prio=5 os_prio=31 tid=0x00007fb854893800 nid=0xa803 waiting for monitor entry [0x0000700002088000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
- waiting to lock <0x000000076ac266a8> (a java.lang.String)
- locked <0x000000076ac26670> (a java.lang.String)
线程2持有锁的id为0x000000076ac266a8
,等待获取的锁的id为0x000000076ac26670
;线程1持有锁的id为0x000000076ac26670
,等待获取的锁的id为0x000000076ac266a8
。线程2和线程1彼此等待,产生死锁。
简单的死锁问题,jstack会直接给出分析结果如下:
Found one Java-level deadlock:
=============================
"线程2":
waiting to lock monitor 0x00007fb8520070a8 (object 0x000000076ac26670, a java.lang.String),
which is held by "线程1"
"线程1":
waiting to lock monitor 0x00007fb850821b58 (object 0x000000076ac266a8, a java.lang.String),
which is held by "线程2"
Java stack information for the threads listed above:
===================================================
"线程2":
at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
- waiting to lock <0x000000076ac26670> (a java.lang.String)
- locked <0x000000076ac266a8> (a java.lang.String)
"线程1":
at com.jk.iw.deadlock.DeadLockFBI.run(DeadLockFBI.java:24)
- waiting to lock <0x000000076ac266a8> (a java.lang.String)
- locked <0x000000076ac26670> (a java.lang.String)
Found 1 deadlock.
复杂的死锁问题可能要仔细分析才能得出结论,但线程栈总体分析过程如下:
- 查找可能有问题的线程状态,如
BLOCKED
、WAITING
。 - 查看等待的目标。
- 对比monitor的持有状态。
程序化分析死锁
Java提供了标准管理API,ThreadMXBean::findDeadlockedThreads
方法用来分析定位死锁。
示例代码如下:
public class DeadLockMonitorFBI extends Thread {
private String la; // 锁a
private String lb; // 锁b
public DeadLockMonitorFBI(String la, String lb, String threadName) {
super(threadName);
this.la = la;
this.lb = lb;
}
@Override
public void run() {
synchronized (la) {
System.out.println("当前线程:" + this.getName() + ", 获得锁:" + la);
synchronized (lb) {
System.out.println("当前线程:" + this.getName() + ", 获得锁:" + lb);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
ScheduledExecutorService scheduleService = Executors.newScheduledThreadPool(1);
// 定时扫描死锁 启动后5s以后每隔5s执行一次
scheduleService.scheduleAtFixedRate(() -> {
long[] threadIds = tmx.findDeadlockedThreads();
Optional.ofNullable(threadIds).map(ts -> {
ThreadInfo[] tinfos = tmx.getThreadInfo(ts);
Arrays.stream(tinfos).peek(tinfo -> {
System.out.println("死锁线程=>" + tinfo.getThreadName());
}).toArray();
return ts;
});
}, 5, 5, TimeUnit.SECONDS);
//死锁业务代码
String l1 = "lock1";
String l2 = "lock2";
Runnable target;
Thread t1 = new DeadLockMonitorFBI(l1, l2, "线程1");
Thread t2 = new DeadLockMonitorFBI(l2, l1, "线程2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
输出结果如下:
当前线程:线程2, 获得锁:lock2
当前线程:线程1, 获得锁:lock1
死锁线程=>线程2
死锁线程=>线程1
死锁线程=>线程2
死锁线程=>线程1
如何避免死锁
- 尽量避免同时使用多个锁尤其是嵌套使用锁,及时释放持有的锁。
- 如果一定要使用多个锁,设计好锁的获取(释放)顺序。
- 使用带有超时设置的方法,减少出错的概率。