简体   繁体   English

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

[英]Thread lock ordering versus a simple synchronized block

While going through Java Concurrency in Practice book, came across this piece of code where "fromAccount" and ""toAccount" objects are locked one after the other to prevent dynamic lock order deadlock. 在阅读Java Concurrency in Practice一书时,遇到了这段代码,其中“fromAccount”和“toAccount”对象被一个接一个地锁定,以防止动态锁定命令死锁。

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

I am confused as to why this lock ordering is needed at all.If we just wanted to make sure that both the objects are locked at the same time then wouldn't you get the same effect if there was just a regular synchronization block inside which fromAccount and toAccount objects are accessed. 我很困惑为什么需要这种锁定顺序。如果我们只是想确保两个对象同时被锁定,那么如果只有一个常规的同步块,你就不会得到相同的效果访问fromAccount和toAccount对象。 I am sure that I am missing some fundamental concept here. 我相信我在这里错过了一些基本概念。 Thank you for your help. 谢谢您的帮助。

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

Your alternative to the lock-ordering example is what you want to avoid: having a central lock that everything is using, because then you don't get concurrent transfers, everything waits for that one lock and only one transfer can proceed at a time. 你想要避免使用锁定顺序示例的替代方法:拥有一个中央锁,一切都在使用,因为那时你没有得到并发传输,一切都等待一个锁,一次只能进行一次传输。 It's not clear what this is or what its scope can possibly be, but if there are multiple instances of this transfer service then locking doesn't do any good, because one transfer involving one account can go through one instance while another transfer involving that same account can go through another. 目前尚不清楚this是什么或它的范围可能是什么,但如果有这种转移服务的多个实例,那么锁定没有任何好处,因为涉及一个帐户的一个转移可以通过一个实例而另一个涉及该转移的转移帐户可以通过另一个。 Therefore it seems like there can only be one of them, which diminishes your concurrency to one transfer at a time. 因此,似乎只能存在其中一个,这会使您的并发性一次性减少到一次传输。 You won't deadlock, but you won't process a lot of transfers quickly either. 你不会死锁,但你也不会很快处理很多转移。

The idea behind this toy example (which you shouldn't mistake for anything like how anybody would transfer money) is it's trying to get better concurrency by locking on the individual accounts involved in the transfer, because for a lot of transfers the accounts involved aren't involved in other concurrent transfers and you'd like to be able to process them concurrently and maximize your concurrency by minimizing the scope of the locking going on to the individual accounts. 这个玩具示例背后的想法(你不应该误解任何人如何转移资金)是因为它试图通过锁定转移中涉及的个人账户来获得更好的并发性,因为对于大量的转移,所涉及的账户都不是参与其他并发传输,您希望能够同时处理它们,并通过最小化锁定到各个帐户的锁定范围来最大化并发性。 But this scheme runs into trouble if some account is involved in multiple concurrent transfers and the locks are acquired in a different order for some transfers. 但是,如果某个帐户涉及多个并发传输,并且对于某些传输以不同的顺序获取锁,则此方案会遇到麻烦。

First, it should be noted that the example you have brought (based on your comment, it's page 208, listing 10.2) is a bad example - one that ends in a deadlock. 首先,应该注意的是你带来的例子(基于你的评论,它是第208页,列出10.2)是一个糟糕的例子 - 一个以死锁结束的例子。 The objects are not locked one after the other to prevent dynamic lock order deadlock, they are an example of where dynamic lock order will happen! 对象不会一个接一个地锁定以防止动态锁定顺序死锁,它们是动态锁定顺序发生的示例!

Now, you are suggesting locking on this , but what is this this anyway, and what is the scope of locking? 现在,你的建议锁定在this ,但是这是什么this反正,什么是锁定的范围是什么?

  • It's clear that the same object has to be used for all operations - withdraw, deposit, transfer. 很明显,同一个对象必须用于所有操作 - 撤销,存款,转移。 If separate objects are used for them, then one thread could do a deposit on account A, while another thread transfers from account A to account B, and they won't be using the same lock so the balance will be compromised. 如果使用单独的对象,则一个线程可以在账户A上存款,而另一个线程从账户A转账到账户B,并且他们将不会使用相同的锁定,因此余额将受到损害。 So the lock object for all accesses to the same account should be the same one. 因此,对同一帐户的所有访问的锁定对象应该是相同的。
  • As Nathan Hughes explained, one needs to localize the locking. 正如Nathan Hughes解释的那样,人们需要将锁定本地化 We can't use one central lock object for all the accounts, or we'll have them all waiting for each other despite not actually working on the same resources. 我们不能为所有帐户使用一个中央锁定对象,或者我们将让它们全部等待彼此,尽管实际上并没有使用相同的资源。 So using a central locking object is also out of the question. 因此,使用中央锁定对象也是不可能的。

So it appears that we need to localize the locks so that each account's balance will have its own lock, so as to allow parallel operations between unrelated accounts, but that this lock has to be used for all operations - withdraw, deposit and transfer. 所以看来我们需要本地化锁,以便每个帐户的余额都有自己的锁,以便允许不相关帐户之间的并行操作,但是这个锁必须用于所有操作 - 撤销,存入和转移。

And here comes the problem - when it's just withdraw or deposit, you are operating on just one account, and so you need to just lock that account. 问题就出现了 - 当它只是撤销或存款时,您只在一个帐户上操作,因此您只需锁定该帐户即可。 But when you transfer , you have two objects involved. 但是当你转移时 ,你有两个对象。 So you need to have both their balances locked in case there are other threads that want to operate on either. 所以你需要锁定它们的余额,以防有其他线程想要操作。

Any object that holds a single lock for two or more accounts will break one of the two points above. 对两个或多个帐户持有单个锁的任何对象都将破坏上述两点之一。 Either it won't be used for all operations, or it will not be localized enough. 要么它不会用于所有操作,要么它不会足够本地化。

This is why they are attempting to lock the two locks one after another. 这就是他们试图一个接一个地锁定两个锁的原因。 Their solution was to make the Account object itself the lock for the account - which fulfils both the "all operations" condition and the "locality" condition. 他们的解决方案是使Account对象本身成为Account的锁定 - 它满足“所有操作”条件和“位置”条件。 But still we need to make sure we have the locks for both accounts before we can transfer the money. 但在我们转账之前,我们仍然需要确保我们拥有两个账户的锁。


But again, this source is an example of a deadlock prone code . 但同样,这个源代码是一个容易出现死锁的代码 This is because one thread may want to transfer from account A to account B, while another will want to transfer from account B to account A. In that case, the first one locks the A account, the second locks the B account, and then they are deadlocked because they have performed the locking in opposite order. 这是因为一个线程可能想要从帐户A转移到帐户B,而另一个线程想要从帐户B转移到帐户A.在这种情况下,第一个锁定A帐户,第二个锁定B帐户,然后他们陷入僵局,因为他们以相反的顺序执行了锁定。

The basic fundamental here is to avoid race condition . 这里的基本原则是避免竞争条件 In your case if there will be another method in any other class who is also doing transfer money to toAccount then incorrect amount may get update in the toAccount . 在您的情况下,如果在任何其他类中还有另一种方法也在转账到账户,那么不正确的金额可能会在toAccount中更新。 eg There are 2 classes which performs money transfer. 例如,有2个班级进行汇款。

One class has a method: 一个类有一个方法:

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

and other class contains: 和其他类包含:

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

If both method takes place at the same time, due to race condition incorrect amount may get update in toAccount. 如果两种方法同时发生,由于竞争条件不正确,金额可能会更新到帐户。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM