我正在实施一个并行银行系统,其中所有操作可以同时运行。我实现了一个线程安全的transferMoney
方法,该方法将amount
从帐户from
转移到to
。
transferMoney
通过以下代码实现:
public boolean transferMoney(Account from, Account to, int amount) {
if (from.getId() == to.getId()){
return false;
}else if(from.getId() < to.getId()) {
synchronized(to) {
synchronized(from) {
if(from.getBalance() >= amount) {
from.setBalance(from.getBalance()-amount);
to.setBalance(to.getBalance()+amount);
}else {
return false;
}
}
}
}else {
synchronized(from) {
synchronized(to) {
if(from.getBalance() >= amount) {
from.setBalance(from.getBalance()-amount);
to.setBalance(to.getBalance()+amount);
}else {
return false;
}
}
}
}
return true;
}
为防止死锁,我已指定始终以相同顺序获取锁。为了确保以相同的顺序获取锁,我使用了ID
中唯一的Account
。
此外,我实现了一种方法,该方法使用以下代码对银行的总金额进行汇总:
public int sumAccounts(List<Account> accounts) {
AtomicInteger sum = new AtomicInteger();
synchronized(Account.class) {
for (Account a : accounts) {
sum.getAndAdd(a.getBalance());
}
}
return sum.intValue();
}
当我与sumAccounts()
同时运行transferMoney()
时,即使没有添加任何钱,我最终也会在银行中得到更多(有时更少)的钱。据我了解,如果我通过Account
锁定了所有synchronized(Account.class)
个对象,我是否应该因为阻止transferMoney()
的执行而得到了正确的库金额?
我尝试了以下操作:
Account.class
(无效)for each
循环中同步特定帐户(但是,由于交易是同时发生的,因此这当然不是线程安全的)ReentrantLock
对象同步这两种方法。可以,但是对性能造成了很大的影响(是顺序代码的三倍) Account.class
上的锁定是否不能阻止进一步的transferMoney()
执行?如果没有,我该如何解决此问题?
编辑:
getBalance()
的代码:
public int getBalance() {
return balance;
}
答案 0 :(得分:1)
在这种情况下,您可以使用ReadWriteLock。 transferMoney方法将使用读取锁,因此可以同时执行。 sumAccounts方法将使用写锁,因此在执行时,不能从其他线程执行transferMoney(或sumAccounts)。
使用ReentrantLock并在类级别同步这两个方法,其行为将与您声明的相同,因为它们不允许并发执行transferMoney方法。
示例代码:
t
重入锁的公平模式也将比非公平模式执行得更慢。查看文档以了解详细信息。
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
答案 1 :(得分:1)
如注释中所述,对一个类对象进行锁定不会对该类的所有实例都进行锁定,而只会对代表您的Account类的Class对象进行锁定。该锁与帐户对象上的锁不兼容,因此根本没有进行同步。
可以在您的for循环内(以sumAccounts的形式)对单个Account对象进行锁定,但这不会阻止此类计划的发生:
- sumAccounts locks 'first' Account and reads balance (and releases lock again at end of the synchronized block taking the lock)
- system schedules a moneyTransfer() from 'first' to 'last'
- sumAccounts locks 'last' Account and reads balance, which includes the amount that was just transferred from 'first' and was already included in the sum
因此,如果您也想避免这种情况,那么您也需要同步Account.class上的moneyTransfer()处理(这样就不必再锁定单个对象了)。
答案 2 :(得分:0)
很难检查您的代码,因为我们无法知道您同步的对象帐户在所有功能中是否完全相同。
首先,我们必须同意余额总和和金额转移是否应该同时运行两个操作。
我希望转帐前后的余额总和是相同的。
另外,您在余额总计中使用synchronized(Account.class)
,这是错误的。您应该在要循环的对象上进行同步。
现在,即使您确实在完全相同的情况下进行协调,您仍然可以拥有以下时间表:
Thread-1 (transfer)
locks from
Thread-2 (sum balance)
locks first object in the list and adds the balance to the running sum and moves to next object
Thread-1
locks to (which is the object Thread-2) processed
moves money from => to
您已经将to
与增加前的金额相加,并且可能要根据时间表将from
与扣除后的金额相加。
问题是您要在传输中更新2个对象,但只能锁定1个对象。
我建议的是:
transfer
方法时设置一些脏标志,如果设置了该标志,请在余额总和中跳过它们,并在完成所有更新后完成总和