【问题标题】:Is this the correct approach to achieve concurrency?这是实现并发的正确方法吗?
【发布时间】:2019-05-15 05:45:28
【问题描述】:

我创建了一个小应用程序,其中基本上包括用于帐户之间转账的 rest APIs。为简单起见,我对数据存储使用了并发 hashmap。现在要实现的非常基本的概念是多线程。

由于我没有使用任何数据库,因此我使用可重入锁在 Account 类本身上创建了锁。现在,当我进行交易时,我获得了两个帐户对象(即发送者和接收者)的锁定,然后将其传递给 biconsumer.accept()。

我不确定这是否完全正确(仅就本应用而言)。 也不确定如何为此进行单元测试。

帐户.java

public class Account {

    private ReentrantLock lock = new ReentrantLock();
    private String accountNumber;
    private long accountBalance;

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public long getAccountBalance() {
        return accountBalance;
    }

    public void setAccountBalance(long accountBalance) {
        this.accountBalance = accountBalance;
    }

    public void getLock() {
        this.accountLock.lock();
    }

    public void doUnlock() {
        this.accountLock.unlock();
    }
}

Transfer.java

send(senderAccount, receiverAccount, (x, y) -> {
                    senderAccount.setAccountBalance(senderAccount.getAccountBalance() - (transferAmount));
                    receiverAccount.setAccountBalance(toAccountDto.getAccountBalance() + transferAmount));
                });


public void send(Account senderAccount, Account receiverAccount,
            BiConsumer<Account, Account> action) {
        senderAccount.lock();
        try {
            receiverAccount.lock();
            try {
                biConsumer.accept(senderAccount, receiverAccount);
            } finally {
                receiverAccount.unlock();
            }
        } finally {
            senderAccount.unlock();
        }
    }

就单线程而言,这可以正常工作。 但是我如何对它进行单元测试以检查它是否适用于 10000 个线程。

这个帐户级别锁定也是一个好习惯(就这个应用程序而言),或者我可以做其他事情?

【问题讨论】:

  • 好的。那将是出于测试目的。但是,如果我这样锁定,这是正确的吗?
  • 如果两个账户互相汇款,就会出现死锁。
  • @JohannesKuhn 我想如果我使用 tryLock(5000, TimeUnit.SECONDS);这应该可以解决问题。对吗?
  • 那么你也必须检查返回值。锁是一罐蠕虫。当第一个锁成功而第二个失败时,你应该怎么做?解锁第一个?等待?多久? (一些随机时间可能是最好的答案)。您的系统很快就会变得如此复杂,以至于难以推理。

标签: java multithreading junit reentrantlock


【解决方案1】:

锁是一个不好的工具。

在您发布的示例中,如果两个线程在两个帐户之间汇款,则可能会出现死锁。

final Account acc1 = new Account();
final Account acc2 = new Account();
new Thread(() -> {
    while (true) {
        send(acc1, acc2, (x, y) -> {
            x.setAccountBalance(x.getAccountBalance() - 100);
            y.setAccountBalance(y.getAccountBalance() + 100);
        }); 
    }
}).start();
new Thread(() -> {
    while (true) {
        send(acc2, acc1, (x, y) -> {
            x.setAccountBalance(x.getAccountBalance() - 100);
            y.setAccountBalance(y.getAccountBalance() + 100);
        }); 
    }
}).start();

在某些时候,线程 1 将锁定 acc1,而线程 2 将锁定 acc2

防止这种情况发生的常用方法是按特定顺序获取锁。
在这种情况下,有太多的锁来管理它。

更好的解决方案是使用AtomicLong,但这需要对代码进行一些更改。

public class Account {

    private String accountNumber;
    private AtomicLong accountBalance = new AtomicLong();

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public long getAccountBalance() {
        return accountBalance.get();
    }

    /* Don't use this method if you expect that the balance has not changed since the last get */
    public void setAccountBalance(long accountBalance) {
        this.accountBalance.set(accountBalance);
    }

    /* Changes the balance atomically */
    public void addAccountBalance(long amountToAdd) {
        accountBalance.addAndGet(amountToAdd);
    }   
}

那么你可以这样使用它:

senderAccount.addAccountBalance(-sendAmount);
receiverAccount.addAccountBalance(sendAmount);

因为没有锁,所以不会发生死锁。 虽然每个动作都是原子的,但有一些注意事项:

  • 如果您希望旧值是某个数字,例如大于转账金额, 您可能不得不在循环中使用compareAndSet
  • 如果您需要保证所有帐户的总和始终保持不变,此方法可能会失败(例如,从一个帐户中取出资金,但尚未将其添加到另一个帐户) - 在这种情况下,您需要一个大锁。但这会破坏并发性,对吗?

编写多线程代码时最大的问题是找出你想要提供什么保证——或者什么仍然是一致的状态。然后确保没有线程看到系统处于不一致的状态。

【讨论】:

  • 感谢您提供如此详细的回答。你能检查一下我发布的那个吗?我们可以通过修复订单或锁来解决这样的问题吗?
猜你喜欢
  • 2012-11-11
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-14
  • 1970-01-01
  • 2020-06-27
相关资源
最近更新 更多