原子地抓住多个锁

时间:2017-08-03 06:38:32

标签: java multithreading concurrency

假设我们必须在任何2个账户(在那里的猎人之间)进行转账,作为交易的一部分 并且在典型的多线程环境中会有多个类似的事务同时运行 通常的惯例如下(根据预先设计的惯例保持锁定顺序):

lock account A
lock account B
transfer(A,B)
release B
release A

有没有办法尝试锁定并释放为原子操作?

3 个答案:

答案 0 :(得分:4)

是的:您需要将锁锁定在锁下。换句话说,您需要创建一个锁层次结构。但是这个解决方案效率不高,因为它会降低锁粒度。

在您的情况下,总是以相同的顺序获取锁定就足够了。例如,总是先锁定ID较小的用户。

答案 1 :(得分:0)

交易是ACID定义的原子(A - 原子性)。隔离(至少READ_COMMITED一)保证帐户A可能同时发生的其他交易将等待上一次启动的交易完成。实际上,您不需要显式锁定它们,因为它们将通过内部实现(例如数据库)锁定,并且锁定将更有效,因为它们可以使用乐观锁定技术。

但是只有当他们都参与一个事务上下文时才会这样(例如在JTA环境中)。在这种环境中,您只需在传输方法开始时启动事务,无需锁定帐户A和帐户B.

如果它们不在同一个事务上下文中,你可以引入一些另一个锁定对象但这会显着降低性能,因为线程将被锁定,即使一个人正在使用帐户A和B而另一个正在使用帐户C和D.有一些技巧可以避免这种情况(例如,见ConcurentHashMap,其中 锁在篮子上 - 而不是在整个物体上。)

但是,你的具体例子答案只能是一些一般性的想法,因为例子是简短来检查更多。我认为锁定帐户A和帐户B的变量按特定顺序排列(应该非常谨慎 - 因为这可能会导致潜在的死锁。并且假设不仅有可以与它们一起使用的转移方法 - 它确实是高风险的)是对于特定情况,这是正常的。

答案 2 :(得分:0)

您可以尝试使用以下代码。 注意:它仅适用于两个锁,我不确定如何使其扩展到更多锁。 这个想法是您先锁上一把,然后再尝试锁上第二把。 如果失败,我们知道现在有1个锁可用,而另一个锁正忙。 因此,我们释放第一个锁,然后您将它们反转,因此我们将锁定一个繁忙的锁,并尝试使那个(WAS!)空闲的锁释放(如果仍然可用)。 冲洗并重复。 从统计上讲,此代码无法放入StackOverflow, 我认为处理它并给出错误要比使它循环更好,因为这表明某个地方发生了很大的错误。

public static void takeBoth(ReentrantLock l1,ReentrantLock l2) {
    l1.lock();
    if(l2.tryLock()) {return;}
    l1.unlock();
    try{takeBoth(l2,l1);}
    catch(StackOverflowError e) {throw new Error("??");}
  }
  public static void releaseBoth(ReentrantLock l1,ReentrantLock l2){
    if(!l1.isHeldByCurrentThread()) {l1.unlock();}//this will fail: IllegarMonitorState exception
    l2.unlock();//this may fail, in that case we did not touch l1.
    l1.unlock();    
    }