更多博客请见 我的语雀知识库

一、 准备测试应用

  1. 新建一个测试程序,写一段线程死锁的代码:
import java.util.*;

public class Test {
    static List<Integer> resource1 = new ArrayList<>();
    static List<Integer> resource2 = new ArrayList<>();
    public static void main(String[] args) {
        threadLock();
    }
    public static void threadLock() {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread().getName() + " got resource1 lock.");
                try {
                    Thread.sleep(100); // 模拟工作时间,让死锁更容易观察到
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " trying to get resource2 lock.");
                synchronized (resource2) { // 尝试获取resource2的锁
                    System.out.println(Thread.currentThread().getName() + " got resource2 lock.");
                }
            }
        }, "Thread 1");

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread().getName() + " got resource2 lock.");
                try {
                    Thread.sleep(100); // 模拟工作时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " trying to get resource1 lock.");
                synchronized (resource1) { // 尝试获取resource1的锁
                    System.out.println(Thread.currentThread().getName() + " got resource1 lock.");
                }
            }
        }, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

二、使用Arthas排查问题

  1. 启动 Arthas: java -jar arthas-boot.jar

image.png image.png

  1. 查看总体使用情况: dashboardimage.png 可以看到已经有死锁线程了
  2. 查看总体线程使用情况: threadimage.pngBLOCKED线程数量:2,并且显示了具体的BLOCKED线程
  3. 定位死锁的位置: thread -bimage.png

输出解释:

  • 线程信息Thread 2 的ID为22,当前状态为BLOCKED。这意味着它正在等待获取某个对象锁。
  • 阻塞原因Thread 2被阻塞是因为需要获取的对象java.util.ArrayLIst@1d0be4d0正被另一个线程所持有。
  • 锁持有者:该对象锁java.util.ArrayLIst@1d0be4d0目前被Thread 1(ID为21)持有。
  • Thread 1的状态:虽然输出信息没有直接展示Thread 1的状态,但从上下文可以推断,Thread 1 locak了java.util.ArrayLIst@1d0be4d0Thread 2 lock了java.util.ArrayLIst@1f2efe1c
  • 相互阻塞:最关键的信息是ownd by'thread 1 ,指出Thread 2尝试获取的锁被Thread 1持有,同时说明Thread 1至少在某一点上也尝试获取Thread 2持有的锁(或者导致了其他形式的循环等待),从而形成了死锁。这里的but blocks 1 other threads!暗示了这种相互阻塞的关系。

到目前为止。基本已经定位到死锁的具体原因和位置了。

  1. 查看死锁线程详细信息: thread 21 thread 22image.png

根据上面的信息,我们可以清晰地看到两个线程Thread 1Thread 2都处于阻塞状态,形成了死锁:

  • Thread 1 (Id=21) 的状态也是BLOCKED,它在Test.java:38行尝试获取对象java.util.ArrayLIst@1f2efe1c的锁,而这个锁正被Thread 2持有。
  • Thread 2 (Id=22) 的状态是BLOCKED,它在Test.java:23行尝试获取对象java.util.ArrayLIst@1d0be4d0的锁,但这个锁正被Thread 1持有。

这种相互等待对方释放锁的情形正是死锁的经典表现。每个线程都持有一个锁,并尝试获取对方的锁,导致双方都无法继续执行下去。

  • Thread 1在执行到Test.java的第38行时阻塞,等待java.util.ArrayLIst@1f2efe1c的锁。
  • Thread 2在执行到同文件的第23行时阻塞,等待java.util.ArrayLIst@1d0be4d0的锁。
  • 两个线程互相等待对方释放锁,形成了死锁。

解决这个问题的关键在于打破死锁的四大条件之一。即:

  1. 资源不可重用
  2. 循环等待
  3. 资源不可被抢夺
  4. 吃着碗里的看着锅里的