线程锁定顺序与简单的同步块

时间:2015-08-21 17:52:47

标签: java multithreading locking

在阅读“实践中的Java并发”一书时,遇到了这段代码,其中" fromAccount" "" toAccount"对象被一个接一个地锁定,以防止动态锁定命令死锁。

public void transferMoney(Account fromAccount,Account toAccount) {
    **synchronized (fromAccount) {**
        **synchronized (toAccount) {**
               ........
        }
    }
}

我很困惑为什么需要这种锁定顺序。如果我们只是想确保两个对象同时被锁定,那么如果只有一个对象就不会得到相同的效果常规同步块,其中访问fromAccount和toAccount对象。我相信我在这里错过了一些基本概念。谢谢你的帮助。

public void transferMoney(Account fromAccount,Account toAccount) {
    synchronized (this) {
        fromAccount.someMethod();
        toAccount.someMethod();        
    }
}

3 个答案:

答案 0 :(得分:2)

您想要避免使用锁定顺序示例的替代方法:拥有一个中央锁,一切都在使用,因为那时您没有获得并发传输,一切都等待一个锁,只有一个传输可以继续一时间目前尚不清楚this是什么或它的范围可能是什么,但如果有这种转移服务的多个实例,那么锁定没有任何好处,因为涉及一个帐户的一个转移可以通过一个实例而另一个实例涉及同一帐户的转帐可以通过另一个帐户。因此,似乎只能存在其中一个,这会使您的并发性一次性减少到一次传输。你不会死锁,但你也不会很快处理很多转移。

这个玩具示例背后的想法(你不应该误解任何人如何转移资金)是因为它试图通过锁定转移中涉及的个人账户来获得更好的并发性,因为对于大量转移而言涉及的帐户不参与其他并发传输,并且您希望能够同时处理它们并通过最小化锁定到各个帐户的锁定范围来最大化并发性。但是,如果某个帐户涉及多个并发传输,并且对于某些传输以不同的顺序获取锁,则此方案会遇到麻烦。

答案 1 :(得分:1)

首先,应该注意的是,您带来的示例(根据您的评论,它是第208页,列出10.2)是一个示例 - 一个以死锁结束的示例。对象不会一个接一个地被锁定以防止动态锁定顺序死锁,它们是动态锁定顺序发生的示例!

现在,您建议锁定dataInput = reactive({ extrafilt = c(Coupon_Select(), Sale_Select()) dots = lapply(extrafilt, function(cols) interp(~!is.na(x), .values = list(x = as.name(cols)))) Orders %>% filter(Region %in% Region_Select(), Community.Type %in% Type_Select()) %>% filter_(.dots = dots) }) ,但无论如何这是NULL是什么,锁定的范围是什么?

  • 很明显,同一个对象必须用于所有操作 - 撤回,存款,转移。如果使用单独的对象,则一个线程可以在账户A上存款,而另一个线程从账户A转账到账户B,并且他们将不会使用相同的锁定,因此余额将受到损害。因此,对同一帐户的所有访问的锁定对象应该是相同的。
  • 正如Nathan Hughes解释的那样,人们需要本地化锁定。我们不能为所有帐户使用一个中央锁定对象,或者我们将让它们全部等待彼此,尽管实际上并没有使用相同的资源。因此,使用中央锁定对象也是不可能的。

所以看来我们需要本地化锁,以便每个帐户的余额都有自己的锁,以便允许不相关帐户之间的并行操作,但是这个锁必须用于所有操作 - 撤销,存放和传输。

问题就出现了 - 当它刚刚撤回或存款时,您只在一个帐户上操作,因此您只需锁定该帐户即可。但是当你传输时,你有两个对象。所以你需要锁定它们的余额,以防有其他线程想要操作。

对两个或多个帐户持有单个锁的任何对象都将破坏上述两点之一。要么它不会用于所有操作,要么它不会足够本地化。

这就是他们试图一个接一个地锁定两个锁的原因。他们的解决方案是使this对象本身成为帐户的锁定 - 它满足“所有操作”条件和“位置”条件。但在我们转账之前,我们仍然需要确保我们拥有两个账户的锁。

但同样,此源代码是易遇死锁代码的示例。这是因为一个线程可能想要从帐户A转移到帐户B,而另一个线程想要从帐户B转移到帐户A.在这种情况下,第一个锁定A帐户,第二个锁定B帐户,然后他们陷入僵局,因为他们以相反的顺序执行了锁定。

答案 2 :(得分:0)

这里的基本基础是为了避免 race condition 。在您的情况下,如果在任何其他类别中还有其他方法也在转账到账户,那么不正确的金额可能会在 toAccount 中获得更新。例如有2个班级进行汇款。

一个班级有一个方法:

public void transferMoney(Account fromAccount,Account toAccount) {
    synchronized (this) {
        fromAccount.someMethod();
        toAccount.someMethod();        
    }
}

和其他类包含:

public void transferMoneyNow(Account fromAccount1,Account toAccount) {
    synchronized (this) {
        fromAccount1.someMethod();
        toAccount.someMethod();        
    }
}

如果两种方法同时发生,由于竞争条件不正确,金额可能会更新到帐户。