如何避免两个独立的 class 函数相互调用时出现 LOCK_INVERSION 或 LOCK_ORDER 问题?

How to avoid LOCK_INVERSION or LOCK_ORDER problem in two independent class calling each other function?

我有两个独立的 类 并且两个 类 函数都在互相调用。我已经将同步块放在两个 类 函数上,但是 coverity 给出了关于 LOCK_INVERSION 和 LOCK_ORDER 的错误,或者它可能导致死锁,不确定。

我的生产代码的覆盖率低于错误。

获取锁 'lockname' 与在别处建立的锁顺序冲突。

示例:

package multithreading;

public class Test1 implements Runnable {
    private static Test1 instance = null;

    public static synchronized Test1 getInstance() {
        if (instance == null) {
            instance = new Test1();
            return instance;
        } else {
            return instance;
        }
    }

    private Object lock = new Object();

    public void sendUpdate() {
        synchronized (lock) {
            try {
                Thread.sleep(3000l);
                System.out.println(getClass().getSimpleName() + "sendUpdate");
                Test2.getInstance().send();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
    
    public void sendUpdate1() {
        synchronized (lock) {
            try {
                Thread.sleep(3000l);
                System.out.println(getClass().getSimpleName() + "sendUpdate1");
                Test2.getInstance().send();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }


    public static void main(String[] args) {
        Thread t = new Thread(Test1.getInstance());
        t.start();
    }

    @Override
    public void run() {
        while (true) {
                this.sendUpdate();

        }
    }

}

package multithreading;

public class Test2 implements Runnable {
    private static Object object = new Object();
    private static Test2 instance = null;

    public static synchronized Test2 getInstance() {
        if (instance == null) {
            instance = new Test2();
            return instance;
        } else {
            return instance;
        }
    }
    
    public void send1() {
        synchronized (object) {
            try {
                Thread.sleep(3000l);
                System.out.println(getClass().getSimpleName() + "send1");
                Test1.getInstance().sendUpdate();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void send() {
        synchronized (object) {
            try {
                Thread.sleep(3000l);
                System.out.println(getClass().getSimpleName() + "send");
                Test1.getInstance().sendUpdate();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t = new Thread(Test2.getInstance());
        t.start();
    }

    @Override
    public void run() {
        while (true) {
            Test2.getInstance().send1();
        }

    }
}

Analysis summary report:
------------------------
Files analyzed                 : 2 Total
    Java (without build)       : 2
Total LoC input to cov-analyze : 90
Functions analyzed             : 15
Paths analyzed                 : 71
Time taken by analysis         : 00:00:18
Defect occurrences found       : 1 LOCK_INVERSION

我认为这会导致死锁问题,但我想了解如何在两个相互独立的 类 调用彼此的函数中基本上处理同步。

我们如何维护两个类之间的锁顺序? 我应该使用基于 ENUM 的共享锁但我有点担心性能还是有任何其他方法来处理这种情况?

Coverity 报告的原因是代码包含潜在的死锁错误。具体来说:

  • Test1.sendUpdate() 获取 Test1.lock,然后调用 Test2.send(),后者获取 Test2.object。与此同时,
  • Test2.send() 获取 Test2.object,然后调用 Test1.sendUpdate(),后者获取 Test1.lock.

如果线程 T1 在另一个线程 T2 执行 Test2.send() 的同时执行 Test1.sendUpdate(),则此排序是可能的:

  • T1 获得 Test1.lock.
  • T2 获得 Test2.object.
  • T2 尝试获取 Test1.lock,但因为它已被 T1 持有而阻止。
  • T1 尝试获取 Test2.object,但因为它已被 T2 持有而阻止。
  • 两个线程都无法取得进展;这是一个僵局。

当单个线程必须同时持有多个锁时,典型的防止死锁的方法是保证获取锁的顺序始终一致。在这里,这意味着总是首先获取 Test1.lock,或者总是首先获取 Test2.object

在您的代码中执行此操作的一种方法是更改​​ Test2.send(),使其首先获取 Test1.lock

public void send() {                 // in class Test2
    Test1 test1 = Test1.getInstance();
    synchronized (test1.lock) {      // Test1.lock first  <-- ADDED
        synchronized (object) {      // Test2.object second
            try {
                ...                  // same code as before
                test1.sendUpdate();
                ...
            }
        }
    }
}

send()中,对sendUpdate()的调用将第二次获取Test1.lock。没关系,因为 Java 中的锁是 recursive.

以上代码要求 Test1.lockpublic,这可能是本例中最简单的修复方法。如果 public 数据成员是一个问题,那么公开 getter 当然很容易。

或者,您可以将现有的一对锁替换为一个保护两者的锁 类。这样可以避免死锁,但会减少并发执行的机会。如果不进一步了解它的实际作用(这可能是一个新问题的主题),就不可能说这是否会显着影响应用程序性能。