gpt4 book ai didi

java - 测试应用程序是否线程安全

转载 作者:搜寻专家 更新时间:2023-10-31 20:16:42 24 4
gpt4 key购买 nike

我有一个简单的应用程序,可以模拟从一个帐户到另一个帐户的汇款。我想编写一个测试来证明它不是线程安全的。

线程有可能以这样的方式进行传输将完成两次。双线程场景:

  • acc1 = 1000$
  • acc2 = 0$
  • 转账600$

    1. T1:获得acc1余额(1000)
    2. T2:获取acc1余额(1000)
    3. T1:从 account1 (400) 中减去 600$
    4. T2:从 account2 (400) 中减去 600$
    5. T1:将 acc2 增加 600 美元 (600)
    6. T2:将 acc2 增加 600 美元 (1200)

目前我的应用程序不支持多线程,它应该会失败,这对我来说很好。我能够用调试器模拟错误。但是,当我进行线程测试时,它总是成功的。我尝试了不同数量的线程、 sleep 、可调用任务

@Test
public void testTransfer() throws AccountNotFoundException, NotEnoughMoneyException, InterruptedException {
Callable<Boolean> callableTask = () -> {
try {
moneyTransferService.transferMoney(ACCOUNT_NO_1, ACCOUNT_NO_2, TRANSFER_AMOUNT);
return true;
} catch (AccountNotFoundException | NotEnoughMoneyException e) {
e.printStackTrace();
return false;
}
};

List<Callable<Boolean>> callableTasks = new ArrayList<>();
int transferTries = 2;
for(int i = 0; i <= transferTries; i++) {
callableTasks.add(callableTask);
}

ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.invokeAll(callableTasks);

Assert.assertEquals(ACCOUNT_BALANCE_1.subtract(TRANSFER_AMOUNT), accountRepository.getByAccountNumber(ACCOUNT_NO_1).get().getBalance());
Assert.assertEquals(ACCOUNT_BALANCE_2.add(TRANSFER_AMOUNT), accountRepository.getByAccountNumber(ACCOUNT_NO_2).get().getBalance());
}

这是汇款代码:

public void transferMoney(String accountFrom, String accountTo, BigDecimal amount) throws AccountNotFoundException, NotEnoughMoneyException {
Account fromAccount = getAccountByNumber(accountFrom);
Account toAccount = getAccountByNumber(accountTo);

if (isBalanceSufficient(amount, fromAccount)) {

//TODO this should be thread safe and transactional
BigDecimal fromNewAmount = fromAccount.getBalance().subtract(amount);
fromAccount.setBalance(fromNewAmount);

// it's possible to fail junits with sleep but I dont want it in code obviously
// Random random = new Random();
// try {
// Thread.sleep(random.nextInt(100));
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }

BigDecimal toNewAmount = toAccount.getBalance().add(amount);
toAccount.setBalance(toNewAmount);

} else {
throw new NotEnoughMoneyException("Balance on account: " + fromAccount.getNumber() + " is not sufficient to transfer: " + amount);//TODO add currency
}
}

最佳答案

欢迎来到精彩的多线程世界。正如评论所指出的,如果没有完整的源代码,将很难确定一种证明任何事物的方法。

但也很难引发线程错误。多线程的第一条规则是您无法通过练习(例如单元)测试证明(或轻易反驳)代码是线程安全的。

不安全的代码可能会执行十亿次而不会出错。在某些平台上,它实际上可能是线程安全的,但在其他平台上却始终失败。当您开始使用线程时,所有 Java 代码在所有平台上的行为都相同的想法就会消失。

此代码(您类(class)的发明内容)在 Java 中不能保证线程安全:

balance+=transaction;

但它也是这么小的一段代码,它在某些平台上可能是安全的,或者运行得如此之快以至于可以无错误地运行数十亿次。

int temp=balance;
Thread.sleep(1000);
balance=temp+transaction;

在大多数平台上最终失败的可能性很大。 那又怎样?它对原始代码行没有任何证明,有时引入延迟会掩盖问题,尤其是在其他地方。

验证或使多线程代码无效的唯一方法是静态分析和对语言保证的充分了解。

您可以尝试在高负载下运行(比方说)两倍于您的平台实际可以并行运行的线程数,并对底层代码进行一些猜测,您很有可能会引发问题。但有些错误可能仅在低负载或介于两者之间的任何负载时发生。

请记住,如果您修改代码重新测试并且它有效,您什么都没证明。我并不是说您不应该将此类测试作为最终检查。

但是永远不要想象单元测试以它们帮助单线程的方式帮助证明多线程的可靠性。这尤其是因为不同的平台可能具有不同的配置(例如进程数、缓存级别、内核)并经历不同级别的负载。

关于java - 测试应用程序是否线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54514934/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com