Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 这是实现并发性的正确方法吗?_Java_Multithreading_Junit_Reentrantlock - Fatal编程技术网

Java 这是实现并发性的正确方法吗?

Java 这是实现并发性的正确方法吗?,java,multithreading,junit,reentrantlock,Java,Multithreading,Junit,Reentrantlock,我创建了一个小应用程序,它基本上包括用于账户间资金转账的RESTAPI。为了简单起见,我将并发hashmap用于数据存储。现在要实现的基本概念是多线程 由于我没有使用任何数据库,所以我使用可重入锁在Account类本身上创建了锁。现在,当我进行交易时,我将获得帐户对象(即发送方和接收方)上的锁,然后将其传递给biconsumer.accept() 我不确定这是否完全正确(仅就本应用而言)。 也不知道如何对此进行单元测试 Account.java public class Account {

我创建了一个小应用程序,它基本上包括用于账户间资金转账的RESTAPI。为了简单起见,我将并发hashmap用于数据存储。现在要实现的基本概念是多线程

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

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

Account.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();
        }
    }
send(senderAccount,receiverAccount,(x,y)->{
senderAccount.setAccountBalance(senderAccount.getAccountBalance()-(转账金额));
receiverAccount.setAccountBalance(toAccountDto.getAccountBalance()+转账金额));
});
公共作废发送(账户发送方账户、账户接收方账户、,
双消费者行动){
senderAccount.lock();
试一试{
receiverAccount.lock();
试一试{
双消费者接受(发送方账户、接收方账户);
}最后{
receiverAccount.unlock();
}
}最后{
senderAccount.unlock();
}
}
就单线程而言,这和预期的一样好。 但是我如何对它进行单元测试,以检查它是否可以在10000个线程上正常工作呢


此外,这个帐户级别的锁定是一个好的做法(就这个应用而言),还是我可以做其他事情?

锁定是一个不好的工具

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

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
上锁定

防止这种情况的通常方法是按一定的顺序获取锁。
在这种情况下,有太多的锁无法管理

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

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
  • 如果您需要保证所有帐户的总和始终保持不变,则此方法可能会失败(例如,从一个帐户中删除资金,但尚未将其添加到另一个帐户)-在这种情况下,您需要一个大锁。但这将击败并发性,对吗

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

您可以求助于:Ok。那将是为了测试目的。但是,如果我这样锁定,这是正确的吗?如果两个帐户互相汇款,您可能会出现死锁。@JohannesKuhn我想如果我使用tryLock(5000,TimeUnit.SECONDS);这应该可以解决问题。对吧?那你也得检查返回值。锁是一罐虫子。当第一个锁成功而第二个锁失败时,您应该怎么做?解锁第一个?等待多久?(一些随机时间可能是最好的答案)。您的系统将很快变得如此复杂,以至于很难对其进行推理。感谢您提供如此详细的答案。你能查一下我贴的那张吗?我们可以通过修改订单或锁来解决这样的问题吗?