我有一个 Bank
类,其中包含一个 Account
列表。银行有一个 transfer()
方法可以将一个账户的值转移到另一个账户。这个想法是在转账中同时锁定from
和to
帐户。
为了解决这个问题,我有以下代码(请记住这是一个非常简单的例子,因为它只是一个例子):
public class Account {
private int mBalance;
public Account() {
mBalance = 0;
}
public void withdraw(int value) {
mBalance -= value;
}
public void deposit(int value) {
mBalance += value;
}
}
public class Bank {
private List<Account> mAccounts;
private int mSlots;
public Bank(int slots) {
mAccounts = new ArrayList<Account>(Collections.nCopies(slots, new Account()));
mSlots = slots;
}
public void transfer(int fromId, int toId, int value) {
synchronized(mAccounts.get(fromId, toId)) {
synchronized(mAccounts.get(toId)) {
mAccounts.get(fromId).withdraw(value);
mAccounts.get(toId).deposit(value);
}
}
}
}
这有效,但不能防止死锁。要解决此问题,我们需要将同步更改为以下内容:
synchronized(mAccounts.get(Math.min(fromId, toId))) {
synchronized(mAccounts.get(Math.max(fromId, toId))) {
mAccounts.get(fromId).withdraw(value);
mAccounts.get(toId).deposit(value);
}
}
但是编译器警告我有关嵌套同步块(synchronized block),我相信这是一件坏事?另外,我不太喜欢最大/最小解决方案(我不是提出这个想法的人),如果可能的话我想避免这种情况。
如何解决上述两个问题?如果我们可以锁定多个对象,我们将同时锁定 from
和 to
帐户,但我们不能这样做(据我所知)。那有什么解决办法呢?
最佳答案
我个人更愿意避免除最微不足道的同步场景外的任何情况。在像你这样的情况下,我可能会使用同步队列集合将存款和取款汇集到一个单线程进程中,该进程操纵你的未 protected 变量。这些队列的“有趣”之处在于,当您将所有代码放入您放入队列的对象中时,从队列中拉出对象的代码绝对是微不足道且通用的(commandQueue.getNext().execute();) -- 然而,正在执行的代码可以任意灵活或复杂,因为它有一个完整的“命令”对象来实现它——这是 OO 风格编程所擅长的模式。
这是一个很好的通用解决方案,无需显式同步就可以解决很多线程问题(同步仍然存在于您的队列中,但通常是最小的并且没有死锁,通常只有“put”方法需要同步所有,这是内部的)。
另一种解决一些线程问题的方法是确保你可能写入的每个共享变量只能由单个进程“写入”,然后你通常可以完全停止同步(尽管你可能需要分散一些周围的瞬变)
关于java - 更好的解决方案而不是 Java 中的嵌套同步块(synchronized block)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7829271/