Java 线程死锁的定位与分析

线程死锁的介绍

线程死锁是指在多线程编程中,两个或多个线程相互持有对方所需要的资源,而又同时等待对方释放资源,导致它们都无法继续执行的情况。在这种情况下,线程将被永久地阻塞,程序也无法正常执行下去。线程死锁是多线程编程中常见的问题,解决方法通常包括谨慎设计资源获取的顺序、使用超时机制、以及资源分配的策略等。

产生线程死锁的原因

  • 系统资源不足
  • 资源分配不当
  • 线程运行推进的顺序不对

产生线程死锁的四个必要条件

  • 互斥
    • 解决方法:把互斥的共享资源封装成可同时访问(并发访问)
  • 占有且等待
    • 解决方法:在线程请求资源时,要求它不占有任何其它资源,也就是它必须一次性申请到所有的资源,这种方式会降低资源的利用效率
  • 非抢占式
    • 解决方法:如果线程不能立即分配到所需的全部资源,要求它不占有任何其他资源,也就是说只能够在同时获得所有需要的资源时,才执行资源分配操作
  • 循环等待
    • 解决方法:对资源进行排序,要求线程按顺序请求资源

编写产生线程死锁的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class HoldLockThread implements Runnable {

private final String lockA;
private final String lockB;

public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + " get " + lockA);
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + " get " + lockB);
}
}
}

}

public class DeadLockDemo {

public static void main(String[] args) {
String lockA = new String("lockA");
String lockB = new String("lockB");
new Thread(new HoldLockThread(lockA, lockB), "T1").start();
new Thread(new HoldLockThread(lockB, lockA), "T2").start();
}

}

程序运行输出的结果:

1
2
3
T1 get lockA
T2 get lockB
程序卡住,线程死锁 ....

线程死锁代码定位与分析

当 Java 程序出现死锁的时候,首先需要使用 jps 命令,查看当前正在运行的 Java 应用程序的进程 ID (PID)

1
jps -l

可以看到 DeadLockDemo 这个 Java 应用程序一直在运行,且进程 ID 是 73690

1
2
3
4
5
483 sun.tools.jps.Jps
72677 com.intellij.idea.Main
73669 org.jetbrains.jps.cmdline.Launcher
73254 org.jetbrains.idea.maven.server.RemoteMavenServer36
73690 com.java.interview.lock.DeadLockDemo

然后使用 jstack 命令查看 Java 应用程序的堆栈信息

1
jstack  73690    // 后面的数字是 jps 命令输出的 Java 应用程序的进程 ID (PID)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Found one Java-level deadlock:
=============================
"T1":
waiting to lock monitor 0x00007f3794008200 (object 0x000000062ca6cfe8, a java.lang.String),
which is held by "T2"
"T2":
waiting to lock monitor 0x00007f3794007f00 (object 0x000000062ca6cfa0, a java.lang.String),
which is held by "T1"

Java stack information for the threads listed above:
===================================================
"T1":
at com.java.interview.lock.HoldLockThread.run(DeadLockDemo.java:28)
- waiting to lock <0x000000062ca6cfe8> (a java.lang.String)
- locked <0x000000062ca6cfa0> (a java.lang.String)
at java.lang.Thread.run(java.base@11.0.9/Thread.java:834)
"T2":
at com.java.interview.lock.HoldLockThread.run(DeadLockDemo.java:28)
- waiting to lock <0x000000062ca6cfa0> (a java.lang.String)
- locked <0x000000062ca6cfe8> (a java.lang.String)
at java.lang.Thread.run(java.base@11.0.9/Thread.java:834)

Found 1 deadlock.

通过查看最后一行的堆栈信息,可以看到 Found 1 deadlock,即表示 Java 应用程序存在一个死锁