如何避免嵌套同步和由此产生的死锁

问题描述 投票:2回答:5

我需要在一个功能中锁定两个对象,当前代码看起来像这样;

Object obj1  = ...//get from somewhere
Object obj2 = ...//get from somewhere

synchronized(obj1){
  ...//blah
  synchronized(obj2){
     ...//blah
  }
}

如您所见,如果另一个线程使用obj1和两个相反的代码运行这段代码,则这是死锁的简单明了的配方。有没有一种方法可以使用concurrency-utils锁来避免这种情况?

我正在考虑维护一个对象及其锁的映射,并验证它们是否可供使用,但似乎无法提出一种可以预测锁顺序的干净方法。

java concurrency locks
5个回答
6
投票

尽管您保留锁定顺序,但是如果将obj1与obj2切换,则会陷入死锁。

您必须寻找另一种解决方案来避免这种情况:锁排序+可选的平局打破锁

int fromHash = System.identityHashCode(obj1);
int toHash = System.identityHashCode(obj2);

if (fromHash < toHash) {
    synchronized (obj1) {
        synchronized (obj2) {
               ........
        }
    }
} else if (fromHash > toHash) {
    synchronized (obj2) {
        synchronized (obj1) {
            ........
        }
    }
} else {
    synchronized (TIE_LOCK) {
        synchronized (fromAcct) {
            synchronized (toAcct) {
               ...
            }
        }
    }

3
投票

取决于您在做什么,您可能可以从第一个锁定对象中获取所需的内容,并使用该信息来处理第二个锁定对象。例如

代替

synchronized(list1) {
  for(String s : list1) {
     synchronized(list2) {
       // do something with both lists.
     }
  }
}

执行此操作

List<String> listCopy;
synchronized(list1) {
  listCopy = new ArrayList<String>(list1);
}

synchornized(list2) {
   // do something with liastCopy and list2
}

您一次只能看到锁,这样就不会出现死锁。


2
投票

您需要始终按照obj1,然后obj2的顺序锁定。如果您从不违反此命令,则不会出现死锁。


1
投票

基本上,您所拥有的是用餐哲学家的问题。

https://en.wikipedia.org/wiki/Dining_philosophers_problem

Ovidiu Lupas的答案类似于Dijkstra的Resource Heirarchy解决方案,但还有3个解决方案,在Wiki页面上进行了说明

这是仲裁器解决方案的外观。如果您要操作的所有对象都继承自同一类型,则可以使用静态类变量在对象类上实现仲裁程序。

import java.util.concurrent.locks.Lock;

public void init()
{
  Lock arbitrator = new Lock();
}

public void meth1()
{
  arbitrator.lock();
  synchronized (obj1) {
    synchronized (obj2) {
      arbitrator.unlock();
      // Do Stuff
    }
  }
}

public void meth2()
{
  arbitrator.lock();
  synchronized (obj2) {
    synchronized (obj1) {
      arbitrator.unlock();
      // Do Stuff
    }
  }
}

Chandy / Misra解决方案需要传递大量消息,所以我不打算实现它,但是维基百科有一个很好的解释


0
投票

您可以用我想的其他方式解决。

class Obj implements Comparable<Obj> {
    // basically your original class + compare(Obj other) implementation
}

class ObjLock implements Lock, Comparable<ObjLock> {

    private final Lock lock;
    private final Obj obj; // your original object

    ObjLock(Obj obj) {
        this.obj = obj;
        this.lock = new ReentrantLock();
    }

    @Override
    public int compare(ObjLock other) {
         return this.obj.compare(other.obj); // ObjLock comparison based on Obj comparison
    }

    // + reimplement Lock methods with this.lock invocations

}

然后做

class ObjLocksGroup {

    private final List<ObjLock> objLocks;

    ObjLocksGroup(ObjLock... objLocks) {
        this.objLocks = stream(objLocks)
                .sorted() // due to ObjLock implements Comparable and sorting you are sure that order of ObjLock... will always be the same
                .collect(toList));
    }

    void lock() {
        this.objLocks.forEach(ObjLock::lock);
    }

    void unlock() {
        this.objLocks.forEach(ObjLock::unlock);
    }
}

并根据需要使用它:

ObjLocksGroup locks = new ObjLocksGroup(obj1, obj2) // the same as obj2, obj1, order does not matter anymore.
locks.lock();
locks.unlock();
© www.soinside.com 2019 - 2024. All rights reserved.