线程锁排序与简单的同步块
Thread lock ordering versus a simple synchronized block
在阅读 Java Concurrency in Practice 书时,遇到了这段代码,其中 "fromAccount" 和“"toAccount" 对象被一个接一个地锁定以防止动态锁定顺序死锁.
public void transferMoney(Account fromAccount,Account toAccount) {
**synchronized (fromAccount) {**
**synchronized (toAccount) {**
........
}
}
}
我很困惑为什么在 all.If 需要这个锁顺序我们只是想确保两个对象同时被锁定那么如果有的话你不会得到相同的效果吗只是一个常规的同步块,在其中访问 fromAccount 和 toAccount 对象。我确信我在这里遗漏了一些基本概念。感谢您的帮助。
public void transferMoney(Account fromAccount,Account toAccount) {
synchronized (this) {
fromAccount.someMethod();
toAccount.someMethod();
}
}
您要避免使用锁排序示例的替代方法:拥有一个一切都在使用的中央锁,因为这样您就不会获得并发传输,一切都在等待那个锁,只有一个传输可以一次进行。目前尚不清楚 this
是什么或它的范围可能是什么,但如果此传输服务有多个实例,则锁定不会有任何好处,因为涉及一个帐户的一次传输可以通过一个实例而另一个涉及同一账户的转账可以通过另一个账户进行。因此,似乎只能有其中之一,这会将您的并发性减少到一次传输。你不会死锁,但你也不会快速处理大量传输。
这个玩具示例背后的想法(你不应该把它误认为任何人如何转账)是它试图通过锁定参与转账的个人账户来获得更好的并发性,因为对于很多transfers 所涉及的帐户不涉及其他并发传输,并且您希望能够并发处理它们并通过最小化对单个帐户的锁定范围来最大化您的并发性。但是,如果某个帐户涉及多个并发传输并且某些传输以不同的顺序获取锁,则此方案会遇到麻烦。
这里的根本是为了避免race condition。在您的情况下,如果任何其他 class 也将向 toAccount 转账,那么 不正确的金额 可能会在 toAccount[ 中得到更新=25=]。例如有 2 个 classes 执行汇款。
一个class有个方法:
public void transferMoney(Account fromAccount,Account toAccount) {
synchronized (this) {
fromAccount.someMethod();
toAccount.someMethod();
}
}
和其他 class 包含:
public void transferMoneyNow(Account fromAccount1,Account toAccount) {
synchronized (this) {
fromAccount1.someMethod();
toAccount.someMethod();
}
}
如果两种方法同时发生,由于竞争条件,不正确的金额可能会在 toAccount 中更新。
首先,应该注意的是您提供的示例(根据您的评论,它是第 208 页,清单 10.2)是一个 不好的 示例 - 以僵局。对象不会一个接一个地锁定以防止动态锁定顺序死锁,它们是发生动态锁定顺序的示例!
现在,您建议锁定 this
,但这 this
到底是什么,锁定的范围是什么?
- 很明显,同一个对象必须用于所有操作 - 取款、存款、转账。如果为它们使用单独的对象,那么一个线程可以在账户 A 上存款,而另一个线程从账户 A 转移到账户 B,并且它们不会使用相同的锁,因此余额会受到损害。所以同一个账号的所有访问的锁对象应该是同一个。
- 正如 Nathan Hughes 所解释的,需要本地化 锁定。我们不能对所有帐户使用同一个中央锁对象,否则我们会让它们互相等待,尽管实际上并没有使用相同的资源。所以用中控锁对象也不行。
所以看来我们需要将锁本地化,让每个账户的余额都有自己的锁,从而允许不相关账户之间的并行操作,但是这个锁必须用于所有操作 - 取款,存款和转账。
问题来了——当它只是取款或存款时,你只在一个账户上操作,所以你只需要锁定那个账户。但是当你传输时,你有两个对象。因此,您需要锁定它们的余额,以防有其他线程想要对其中任何一个进行操作。
任何为两个或多个帐户持有一个锁的对象都会破坏上述两点之一。要么不会用于所有操作,要么本地化不够。
这就是为什么他们要一个接一个地锁上两把锁。他们的解决方案是让 Account
对象本身成为帐户的锁——它同时满足 "all operations" 条件和 "locality" 条件。但是我们仍然需要确保我们在转账之前锁定了两个账户。
但同样,这个来源是容易死锁的代码的一个例子。这是因为一个线程可能想从账户A转账到账户B,而另一个线程想从账户B转账到账户A。在这种情况下,第一个锁定A账户,第二个锁定B账户,然后他们死锁了,因为他们以相反的顺序执行了锁定。
在阅读 Java Concurrency in Practice 书时,遇到了这段代码,其中 "fromAccount" 和“"toAccount" 对象被一个接一个地锁定以防止动态锁定顺序死锁.
public void transferMoney(Account fromAccount,Account toAccount) {
**synchronized (fromAccount) {**
**synchronized (toAccount) {**
........
}
}
}
我很困惑为什么在 all.If 需要这个锁顺序我们只是想确保两个对象同时被锁定那么如果有的话你不会得到相同的效果吗只是一个常规的同步块,在其中访问 fromAccount 和 toAccount 对象。我确信我在这里遗漏了一些基本概念。感谢您的帮助。
public void transferMoney(Account fromAccount,Account toAccount) {
synchronized (this) {
fromAccount.someMethod();
toAccount.someMethod();
}
}
您要避免使用锁排序示例的替代方法:拥有一个一切都在使用的中央锁,因为这样您就不会获得并发传输,一切都在等待那个锁,只有一个传输可以一次进行。目前尚不清楚 this
是什么或它的范围可能是什么,但如果此传输服务有多个实例,则锁定不会有任何好处,因为涉及一个帐户的一次传输可以通过一个实例而另一个涉及同一账户的转账可以通过另一个账户进行。因此,似乎只能有其中之一,这会将您的并发性减少到一次传输。你不会死锁,但你也不会快速处理大量传输。
这个玩具示例背后的想法(你不应该把它误认为任何人如何转账)是它试图通过锁定参与转账的个人账户来获得更好的并发性,因为对于很多transfers 所涉及的帐户不涉及其他并发传输,并且您希望能够并发处理它们并通过最小化对单个帐户的锁定范围来最大化您的并发性。但是,如果某个帐户涉及多个并发传输并且某些传输以不同的顺序获取锁,则此方案会遇到麻烦。
这里的根本是为了避免race condition。在您的情况下,如果任何其他 class 也将向 toAccount 转账,那么 不正确的金额 可能会在 toAccount[ 中得到更新=25=]。例如有 2 个 classes 执行汇款。
一个class有个方法:
public void transferMoney(Account fromAccount,Account toAccount) {
synchronized (this) {
fromAccount.someMethod();
toAccount.someMethod();
}
}
和其他 class 包含:
public void transferMoneyNow(Account fromAccount1,Account toAccount) {
synchronized (this) {
fromAccount1.someMethod();
toAccount.someMethod();
}
}
如果两种方法同时发生,由于竞争条件,不正确的金额可能会在 toAccount 中更新。
首先,应该注意的是您提供的示例(根据您的评论,它是第 208 页,清单 10.2)是一个 不好的 示例 - 以僵局。对象不会一个接一个地锁定以防止动态锁定顺序死锁,它们是发生动态锁定顺序的示例!
现在,您建议锁定 this
,但这 this
到底是什么,锁定的范围是什么?
- 很明显,同一个对象必须用于所有操作 - 取款、存款、转账。如果为它们使用单独的对象,那么一个线程可以在账户 A 上存款,而另一个线程从账户 A 转移到账户 B,并且它们不会使用相同的锁,因此余额会受到损害。所以同一个账号的所有访问的锁对象应该是同一个。
- 正如 Nathan Hughes 所解释的,需要本地化 锁定。我们不能对所有帐户使用同一个中央锁对象,否则我们会让它们互相等待,尽管实际上并没有使用相同的资源。所以用中控锁对象也不行。
所以看来我们需要将锁本地化,让每个账户的余额都有自己的锁,从而允许不相关账户之间的并行操作,但是这个锁必须用于所有操作 - 取款,存款和转账。
问题来了——当它只是取款或存款时,你只在一个账户上操作,所以你只需要锁定那个账户。但是当你传输时,你有两个对象。因此,您需要锁定它们的余额,以防有其他线程想要对其中任何一个进行操作。
任何为两个或多个帐户持有一个锁的对象都会破坏上述两点之一。要么不会用于所有操作,要么本地化不够。
这就是为什么他们要一个接一个地锁上两把锁。他们的解决方案是让 Account
对象本身成为帐户的锁——它同时满足 "all operations" 条件和 "locality" 条件。但是我们仍然需要确保我们在转账之前锁定了两个账户。
但同样,这个来源是容易死锁的代码的一个例子。这是因为一个线程可能想从账户A转账到账户B,而另一个线程想从账户B转账到账户A。在这种情况下,第一个锁定A账户,第二个锁定B账户,然后他们死锁了,因为他们以相反的顺序执行了锁定。