更好的解决方案而不是Java中的嵌套同步块?

我有一个Bank类,里面有Account列表。 银行有一个transfer()方法,用于将值从一个帐户转移到另一个帐户。 我们的想法是锁定转移中的fromto账户。

要解决这个问题,我有以下代码(请记住,这是一个非常简单的例子,因为它只是一个例子):

 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 mAccounts; private int mSlots; public Bank(int slots) { mAccounts = new ArrayList(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); } } 

但编译器警告我嵌套同步块 ,我相信这是一件坏事吗? 另外,我不是非常喜欢最大/最小解决方案(我不是那个提出这个想法的人)而且我想尽可能避免这种情况。

如何解决上述两个问题? 如果我们可以锁定多个对象,我们将锁定fromto帐户,但我们不能这样做(据我所知)。 那么解决方案是什么?

锁定顺序确实是解决方案,所以你是对的。 编译器警告你,因为它无法确保所有锁定都是有序的 – 它不够聪明,无法检查你的代码,并且足够聪明,知道可能还有更多。

另一种解决方案可能是锁定封闭对象,例如,在一个用户的帐户内进行传输,您可以锁定用户。 用户之间的转移不是这样。

话虽如此,您可能不会依赖Java锁定来进行传输:您需要一些数据存储,通常是数据库。 在使用数据库的情况下,锁定移动到存储。 但是,同样的原则仍然适用:您订购锁以避免死锁; 升级锁以使锁定更简单。

我个人更喜欢避免任何但最简单的同步场景。 在像你这样的情况下,我可能会使用同步的队列集合来汇集存款并退回到操作未受保护的变量的单线程进程中。 关于这些队列的“有趣”的事情是当你把所有代码都放入你放入队列的对象中时所以从队列中拉出对象的代码绝对是微不足道的和通用的(commandQueue.getNext()。execute();) – 正在执行的代码可以是任意灵活或复杂的,因为它有一个完整的“Command”对象用于它的实现 – 这是OO风格编程擅长的那种模式。

这是一个很好的通用解决方案,可以在没有显式同步的情况下解决相当多的线程问题(队列中仍然存在同步但通常是最小且无死锁的,通常只需要同步“put”方法,并且这是内部的)。

某些线程问题的另一个解决方案是确保您可能写入的每个共享变量只能由单个进程“写入”,然后您通常可以完全取消同步(尽管您可能需要分散一些瞬态)

我建议你在java中查看Lock Objects。 看看条件对象也是如此。 您的每个帐户对象都可以公开线程等待的条件。 事务完成后,将调用条件对象等待或通知。

如果您还没有,可能需要查看java.util.concurrent中更高级的锁定包。

虽然你仍然需要注意避免死锁,但ReadWriteLocks特别适用于允许multithreading读访问,同时仍然锁定对象修改。

使用Polyglot编程可以轻松实现,使用Clojure中的 软件事务内存但使用Java

软件事务存储器(STM)是一种类似于数据库事务的并发控制技术,用于控制并发计算中对共享存储器的访问。 它是基于锁的同步的替代方案。

示例解决方案

Account.java

 import clojure.lang.Ref; public class Account { private Ref mBalance; public Account() { mBalance = new Ref(0); } public void withdraw(int value) { mBalance.set(getBalance() - value); } public void deposit(int value) { mBalance.set(getBalance() + value); } private int getBalance() { return (int) mBalance.deref(); } } 

Bank.java

 import clojure.lang.LockingTransaction; import java.util.* import java.util.concurrent.Callable; public class Bank { private List mAccounts; private int mSlots; public Bank(int slots) { mAccounts = new ArrayList<>(Collections.nCopies(slots, new Account())); mSlots = slots; } public void transfer(int fromId, int toId, int value) { try { LockingTransaction.runInTransaction( new Callable() { @Override public Object call() throws Exception { mAccounts.get(fromId).withdraw(value); mAccounts.get(toId).deposit(value); return null; } }); } catch (Exception e) { e.printStackTrace(); } } } 

依赖

  org.clojure clojure 1.6.0