Java 死锁原因及排查


在高并发多线程的应用里,很可能就会遇到死锁问题,来看看死锁的原因,遇到了要怎么解决呢?

死锁的原因

Java 发生死锁的根本原因是: 在申请锁时发生了交叉闭环申请

即线程在获得了锁 A 并且没有释放的情况下又去申请锁 B, 这时另一个线程已经获得了锁 B

在释放锁 B 之前又要先获得锁 A, 因此闭环发生, 陷入死锁循环, 这就是死锁了

示例 1

public class DeadLockA extends Thread {
    @Override
    public void run() {
        try{
            System.out.println("LockA 开始运行");
            while(true){
                synchronized(Client.obj1){
                    System.out.println("LockA 锁住 obj1");
                    // 给 LockB 一个锁住 obj2 的机会...
                    Thread.sleep(100);
                    System.out.println("LockA 尝试锁住 obj2...");
                    synchronized(Client.obj2){
                        System.out.println("LockA 锁住 obj2");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

public class DeadLockB extends Thread {
    @Override
    public void run() {
        try{
            System.out.println("LockB 开始运行");
            while(true){
                synchronized(Client.obj2){
                    System.out.println("LockB 锁住 obj2");
                    System.out.println("LockB 尝试锁住 obj1...");
                    synchronized(Client.obj1){
                        System.out.println("LockB 锁住 obj1");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

public class Client {
    public static final String obj1 = "obj1";
    public static final String obj2 = "obj2";

    public static void main(String[] ars) {
        new DeadLockA().start();
        new DeadLockB().start();
    }
}

运行结果

LockA 开始运行
LockA 锁住 obj1
LockB 开始运行
LockB 锁住 obj2
LockB 尝试锁住 obj1...
LockA 尝试锁住 obj2...

两个线程最后都在等待对方释放锁, 最终进入了死锁状态

示例 2

public class TestClass {

    public synchronized void method(TestClass clazz) {
        System.out.println("TestClass method in");
        clazz.method2();
        System.out.println("TestClass method out");
    }

    public synchronized void method2() {
        System.out.println("TestClass method2");
    }
}

public class TestLock extends Thread {
    private TestClass class1;
    private TestClass class2;

    public TestLock(TestClass class1, TestClass class2) {
        this.class1 = class1;
        this.class2 = class2;
    }

    @Override
    public void run() {
        class1.method(class2);
    }
}

public class Client {
    public static void main(String[] ars) {
        TestClass classA = new TestClass();
        TestClass classB = new TestClass();
        new TestLock(classA, classB).start();
        new TestLock(classB, classA).start();
    }
}

运行结果

TestClass method in
TestClass method in

结果显示进入两次方法, 但是并没有走完, 因为死锁了

一旦出现死锁, 整个程序既不会发生任何错误, 也不会给出任何提示, 只是所有线程处于阻塞状态, 无法继续

如何避免死锁

Java 虚拟机没有提供检测, 也没有采取任何措施来处理死锁的情况, 所以多线程编程中, 应该手动采取措施避免死锁

我们知道了死锁如何产生的, 那么就知道该如何去预防

如果一个线程每次只能获取一个锁, 那么就不会出现由于嵌套持有锁顺序导致的死锁

正确的顺序获得锁

上面的例子出现死锁的根本原因就是获取所的顺序是乱序的, 超乎我们控制的

上面例子最理想的情况就是把业务逻辑抽离出来, 把获取锁的代码放在一个公共的方法里面, 让这两个线程获取锁都是从我的公共的方法里面获取

Thread1 线程进入公共方法时, 获取了 A 锁, 另外 Thread2 又进来了, 但是 A 锁已经被 Thread1 线程获取了, 所以只能阻塞等待

Thread1 接着又获取锁 B, Thread2 线程就不能再获取不到了锁 A, 更别说再去获取锁 B 了,这样就有一定的顺序了

只有当 Thread1 释放了所有锁, 线程 B 才能获取


文章作者: CrazyBunQnQ
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 CrazyBunQnQ !
 上一篇
FTX 量化空间 FTX 量化空间
FTX 交易所是一家非常适合做对冲交易的交易所, 并且它有一个非常棒的功能: 量化空间. 即使你没有服务器,没有交易系统,不会编程,也可以用它来创建你的自动交易机器人.
2021-09-29
下一篇 
Java 注解 Java 注解
写代码的时候经常能见到各种注解,可能因为太常见,所以忽略了它的存在。今天点进去几个注解看了下源码注释, 了解下官方说明各个注解是干啥的, 结果发现每个注解类里面的方法都跳转不到实现类, 哎哟我的好奇心呐, 研究研究!
2021-06-01