首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >线程安全银行事务:用Java进行存款、取款、支票余额和转账

线程安全银行事务:用Java进行存款、取款、支票余额和转账
EN

Code Review用户
提问于 2019-11-22 19:59:04
回答 1查看 1.5K关注 0票数 1

我做了一个支持存款、取款、支票余额查询和转账的线程安全实现课程。我有一个班级帐户:

代码语言:javascript
复制
public class Account {
    private int balance;
    private String address;
    private String userId;
    final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    Lock transferLock = new ReentrantLock();
    public Account(String userId){
        this.userId = userId;
    }

    public void add(int amount){
        balance += amount;
    }
    public void sub(int amount){
        this.balance -= amount;
    }
    public int getBalance(){
        return balance;
    }
}

我使用一个ReentrantReadWriteLock来存放和提取,这获得了一个写锁。读取锁是在检查帐户余额时获得的。A 'transferLock‘用于锁定从帐户A转到帐户B的资金。

以下是主要功能:

代码语言:javascript
复制
 public boolean deposit(String userId, int amount){
        Account account = getUserAccount(userId);
        Lock accountWriteLock = account.lock.writeLock();
        accountWriteLock.lock();

        account.add(amount);

        accountWriteLock.unlock();
        return true;
    }

    public boolean withdraw(String userId, int amount){
        Account account = getUserAccount(userId);
        Lock accountWriteLock = account.lock.writeLock();
        accountWriteLock.lock();

        if(account.getBalance() < amount){
accountWriteLock.unlock();
            return false;
        }
        account.sub(amount);

        accountWriteLock.unlock();
        return true;
    }

    public int checkBalance(String userId){
        Account account = getUserAccount(userId);
        Lock accountReadLock = account.lock.readLock();
        accountReadLock.lock();

        int balance = account.getBalance();

        accountReadLock.unlock();
        return balance;
    }


    public boolean transfer(String fromAccountUserId, int amount, String toAccountUserId) throws Exception {
        Account fromAccount = getUserAccount(fromAccountUserId);
        Account toAccount = getUserAccount(toAccountUserId);
        Random rand = new Random();
        Lock fromAccountTransferLock = fromAccount.transferLock;
        Lock toAccountTransferLock = toAccount.transferLock;
        while (true) {
            if (fromAccountTransferLock.tryLock()) {
                fromAccount.lock.writeLock();
                if (toAccountTransferLock.tryLock()) {
                    try {
                        withdraw(fromAccountUserId, amount);
                        deposit(toAccountUserId, amount);
                        break;
                    } catch (Exception e) {
                        return false;
                    } finally {
                        toAccountTransferLock.unlock();
                    }
                }
                fromAccountTransferLock.unlock();
                Thread.sleep(rand.nextInt(1001));
            }
        }
        return true;
    }

假设getUserAccount(String userId)检索适当的帐户对象。

我想回顾的是

  1. 我的代码线程安全吗?

  1. 你觉得有什么改进吗?

备注

  1. 我通过允许保持线程释放其锁来防止从帐户A到帐户B的传输中的死锁。

  1. 我阻止LiveLock的转移(.)通过在万 ms之间休眠线程随机数毫秒。

编辑:下面是getUserAccount()代码:

代码语言:javascript
复制
public Account getUserAccount(String userId){
        return new Account(userId);
    }
EN

回答 1

Code Review用户

发布于 2020-07-13 09:22:20

让我们把这个拆开。

  1. 对于保护相同字段的单个对象,您有两个锁。

这通常是不好的做法,而且它没有意义,因为第二个锁(TransferLock)只在单个操作中使用。它不会保护其他行动。

如果您有一个静态只读锁用于传输,则可以使用以下语义: add/sub/get是全局读锁,传输是全局写锁,那么yu应该在常规操作上获得读锁,在传输op上获得写锁,但这将允许每次进行on;y单个传输,这肯定不是您想要实现的。

  1. 您的锁在主类之外泄漏(reed不是私有/受保护的)。

这可能导致外部的不受控制的使用,说未来版本的代码可能会误用锁。我可以看到,它们具有包级可见性,这是半好的,但是Account#add、Accound#sub、Account#getBallance是公共的,不受锁的保护,因此破坏了拥有包私有的目的。

我建议将锁的所有用法放在上面提到的实例方法中,并添加另一个方法:

代码语言:javascript
复制
class Account{
public void transfer(Account other, int amount) { ... }
}

那么,所有与锁定相关的逻辑都将位于一个单一的受控位置。

  1. 锁具订购。如果您使用的是细粒度锁,那么所有帐户都没有单独的锁,您需要锁定多个对象,您应该有严格的锁顺序。

您正在尝试使用tryLock和then循环来实现这一点,如果您已经使用了rw lock,这实际上是可行的,但是当您获得其他锁时,可能会出现死锁。在旋转循环中等待并不是真正的表演,你可以永远等待。

我建议用户使用您的rw锁,但首先,通过accountId对两个帐户进行排序,然后按照帐户ids从低到高的顺序使用写锁。

代码语言:javascript
复制
//pseudocode
if (acc1.id<acc2.id) {
  acc1.lock.writeLock().lock()
  acc2.lock.writeLock().lock()
}else{
  acc2.lock.writeLock().lock()
  acc1.lock.writeLock().lock()
}

它实际上可以与tryLock相结合,但可能需要一段时间才能获得这样的锁。

  1. 次要的是,最好在最后块中调用unlock(),这样就不会有对象停留在锁定状态。
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/232830

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档