我熟悉在一个帐户和另一个帐户之间转移资金时使用synchronized的并发示例,例如,在帐号上按顺序锁定两个帐户,以便不会发生死锁。
我想探索使用ReentrantReadWriteLock,因为在我看来,如果没有客户端更新对象,这将允许Account对象的客户端进行并发读取。
我已经编写了代码并对其进行了测试,它似乎有效,但它看起来有点难看,例如,对于Account类暴露其锁定对象看起来有点奇怪,但似乎它不得不?还想知道是否有任何潜在的错误我还没有发现。
getBalance()方法是否正确地确保内存可见性?
在getBalance()中,'返回'在尝试看起来很难看,但在锁定仍然存在时必须读取平衡字段?
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void writeLock(){
lock.writeLock().lock();
}
public void readLock(){
lock.readLock().lock();
}
public void writeUnlock(){
lock.writeLock().unlock();
}
public void readUnlock(){
lock.readLock().unlock();
}
public void transferToSafe(Account b, BigDecimal amount){
Account firstAccountToLock=null;
Account secondAccountToLock=null;
// Let the smaller account always get the first lock
if (this.getAccountNo() < b.getAccountNo()){
firstAccountToLock = this;
secondAccountToLock = b;
}
else {
firstAccountToLock = b;
secondAccountToLock = this;
}
try {
firstAccountToLock.writeLock();
try {
secondAccountToLock.writeLock();
this.subtractFromBalance(amount);
b.addToBalance(amount);
}
finally {
secondAccountToLock.writeUnlock();
}
}
finally {
firstAccountToLock.writeUnlock();
}
}
public BigDecimal getBalance(){
try {
this.readLock();
return balance;
}
finally {
this.readUnlock();
}
}
答案 0 :(得分:1)
您的锁定顺序以及您的死锁预防机制,但subtractFromBalance
和addToBalance
有两个建议可以确保正确性(并且您的代码可能已经在执行此操作)
在subtractFromBalance
和addToBalance
内进行验证,即如果IllegalArgumentException
大于amount
的当前余额,则抛出subtractFromBalance
或其他任何内容(我假设不允许负余额)。您之前可能会进行此验证,但在获得锁定后,您还需要执行此操作。
在isHeldByCurrentThread
和subtractFromBalance
内的写锁上调用addToBalance
,如果没有锁定则抛出异常。
如果您能够承受数据中的一些暂时性不一致,那么您可以完全取消读锁定:使用AtomicReference<BigDecimal>
表示余额(getBalance()
只需{{1} }})。不一致的是,如果您从return balance.get()
中减去钱并将其添加到AccountA
,那么同时拨打AccountB
可能会返回getBalance
和旧AccountA
的新余额AccountB
的余额,但这在您的系统中可能是安全的,因为它只会低估可用资金,并且帐户最终会保持一致。