这段代码是否正确使用ReentrantReadWriteLock?

时间:2014-12-21 14:17:37

标签: java concurrency

我熟悉在一个帐户和另一个帐户之间转移资金时使用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();
    }
}

1 个答案:

答案 0 :(得分:1)

您的锁定顺序以及您的死锁预防机制,但subtractFromBalanceaddToBalance有两个建议可以确保正确性(并且您的代码可能已经在执行此操作)

  1. subtractFromBalanceaddToBalance内进行验证,即如果IllegalArgumentException大于amount的当前余额,则抛出subtractFromBalance或其他任何内容(我假设不允许负余额)。您之前可能会进行此验证,但在获得锁定后,您还需要执行此操作。

  2. isHeldByCurrentThreadsubtractFromBalance内的写锁上调用addToBalance,如果没有锁定则抛出异常。

  3. 如果您能够承受数据中的一些暂时性不一致,那么您可以完全取消读锁定:使用AtomicReference<BigDecimal>表示余额(getBalance()只需{{1} }})。不一致的是,如果您从return balance.get()中减去钱并将其添加到AccountA,那么同时拨打AccountB可能会返回getBalance和旧AccountA的新余额AccountB的余额,但这在您的系统中可能是安全的,因为它只会低估可用资金,并且帐户最终会保持一致。