有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

当从多个线程以相反顺序执行equals()时,Java的同步集合出现问题

示例场景

  • 创建两个同步集(s1和s2)
  • 将它们传递给两个线程(T1和T2)
  • 启动线程

T1的run(): 而(永远) s1。等于(s2)

T2的run() 而(永远) s2。等于(s1)

会发生什么? -SynchronizedSet的equals获得自身的锁

  • 它计算传入的参数的长度以及它包含的内容,以确定其是否相等[注意:这是基于我分析的日志的猜测]

  • 如果传入的参数也是SynchronizedSet,那么对size()和containAll()的调用也意味着必须获取该参数的锁

  • 在上例中,T1和T2的锁获取顺序如下:

    T1:s1->;s2 T2:s2->;s1

Ofc,这会导致僵局

此问题并非仅限于同步集合。即使使用哈希表或向量,也可能发生这种情况

我认为这是一个JavaAPI限制(设计)。如何克服这个问题?如何确保我的应用程序中不会发生这种情况?在这种情况下,我是否应该遵循一些设计原则


共 (5) 个答案

  1. # 1 楼答案

    I believe this is a Java API limitation (design).

    我相信你错了。我曾经使用过的每个PL级锁定方案的一个基本要求是,线程必须以相同的顺序锁定资源,否则就有死锁的风险。这也适用于数据库

    事实上,我认为你能避免这种情况的唯一方法是:

    • 要求应用程序获取单个原子操作所需的所有锁,或
    • 使用单个全局锁执行所有锁定

    这两种方法都是不切实际和不可扩展的

    How to overcome this?

    编写应用程序代码,以便所有线程以相同的顺序获取锁@莫里斯和@Nettogrof的答案给出了如何做到这一点的例子,尽管如果你有很多场景需要担心,这可能会更加困难

  2. # 2 楼答案

    可以在每个线程中以相同的顺序锁定两个集合:

                synchronized(s1) {
                    synchronized(s2) {
                        s1.equals(s2);
                    }
                }
    

                synchronized(s1) {
                    synchronized(s2) {
                        s2.equals(s1);
                    }
                }
    
  3. # 3 楼答案

    斯蒂芬·C的是好东西。进一步信息:如果您不知道这两个集合是哪一种方式,可以在比较这两个集合时使用“全局”锁:

     private static final Object lock = new Object(); // May be context-local.
    
     [...]
    
         synchronized (lock) {
             synchronized (s1) {
                 synchronized (s2) {
                     return s1.equals(s2);
                 }
              }
         }
    

    如果这些集合可能会被争用,您可以在大多数情况下按标识哈希代码排序,并返回到全局锁:

        int h1 = System.identityHashCode(s1);
        int h2 = System.identityHashCode(s2);
        return
             h1<h2 ? lockFirstEquals(h1, h2) :
             h2<h1 ? lockFirstEquals(h2, h1) :
             globalLockEquals(h1, h2);
    

    有时,您可以使用另一种算法。IIRC,StringBuffer可以通过append死锁(尽管对象上的操作组合没有多大意义)。可以通过以下方式实施:

    public StringBuffer append(StringBuffer other) {
        if (other == null) {
            return append("null");
        }
        int thisHash  = System.identityHashCode(this);
        int otherHash = System.identityHashCode(other);
        if (thisHash < otherHash) {
            synchronized (this) {
                synchronized (other) {
                    appendImpl(other);
                }
            }
        } else if (otherHash < thisHash) {
            synchronized (other) {
                synchronized (this) {
                    appendImpl(other);
                }
            }
        } else {
            append(other.toString()); // Or append((Object)other);
        }
        return this;
    }
    

    最好的解决方案可能是改变线程策略,这样就不需要在这里进行任何锁定

  4. # 4 楼答案

    我可能建议使用synchronized(){]块

    比如:

    while(forever){
        synchronized(s1){
            s1.equals(s2);
        }
    }
    

    while(forever){
       synchronized(s1){
        s2.equals(s1);
       }
    }
    
  5. # 5 楼答案

    在执行equals()调用之前,可以按集合的^{}排序。这样,锁获取的顺序将始终相同