Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/385.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

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 - Fatal编程技术网

Java 测试应用程序是否是线程安全的

Java 测试应用程序是否是线程安全的,java,multithreading,Java,Multithreading,我有一个简单的应用程序,模拟从一个帐户到另一个帐户的资金转移。我想写一个测试来证明它不是线程安全的 有一种可能性,线程将以这样的方式传输两次。双线程方案: acc1=1000$ acc2=0$ 转帐600$ T1:获取acc1余额(1000) T2:获取acc1余额(1000) T1:从account1(400)中减去600美元 T2:从account2(400)中减去600美元 T1:将acc2增加600美元(600美元) T2:将acc2增加600美元(1200美元) 目前,我的应用程

我有一个简单的应用程序,模拟从一个帐户到另一个帐户的资金转移。我想写一个测试来证明它不是线程安全的

有一种可能性,线程将以这样的方式传输两次。双线程方案:

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

  • T1:获取acc1余额(1000)
  • T2:获取acc1余额(1000)
  • T1:从account1(400)中减去600美元
  • T2:从account2(400)中减去600美元
  • T1:将acc2增加600美元(600美元)
  • T2:将acc2增加600美元(1200美元)
目前,我的应用程序不支持多线程,它应该会失败,这对我来说是好的。我能够用调试器模拟错误。然而,当我进行线程测试时,它总是成功的。我尝试了不同数量的线程、睡眠和可调用任务

@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());
    }

如果只需在测试中引入比mock
getAccountByNumber
超时的时间,例如使用并使mock
Account.setBalance()
花费X秒

您可以看看用于编写并发测试的框架。一个例子

Java并发压力测试(jcstress)是一个实验性工具和一套测试,用于帮助研究JVM、类库和硬件中并发支持的正确性


您可以存根
getAccountByNumber(accountFrom)
并在
getBalance()
代码中实现一些“等待代码”,仅用于测试

无论如何,我建议您将服务代码移动到您的业务对象,即添加方法
Account#transferTo(Account target,BigDecimal amount)

然后,您可以简单地将此方法标记为
已同步
,或者执行一些专用同步


更进一步,您还可以创建一个
公共同步的void accept(BigDecimal amount)
。因此,标记每个平衡更改方法都是同步的。

欢迎来到多线程的奇妙世界。正如一篇评论所指出的,如果没有完整的源代码,很难确定一种证明任何东西的方法

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

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

这段代码(类的虚构内容)在Java中不保证线程安全:

balance+=transaction;
但它也是一段很小的代码,在某些平台上可能是安全的,或者运行速度很快,可以无误地运行数十亿次

int temp=balance;
Thread.sleep(1000);
balance=temp+transaction;
在大多数平台上有很好的机会最终失败那又怎样? 它对原始代码行没有任何证明,有时引入延迟会掩盖问题,特别是在其他地方

验证或使多线程代码无效的唯一方法是静态分析和良好的语言保证知识

您可以尝试在高负载下运行,线程数是您的平台实际并行运行线程数的两倍,并对底层代码进行一些猜测,这样很有可能引发问题。但有些错误可能只发生在低负载或两者之间的任何负载下

请记住,如果您修改了代码重新测试,并且成功了,那么您将一无所获。我不是说你不应该做这样的测试作为最后的检查


但是,千万不要想象单元测试能以一种有助于单线程的方式帮助证明多线程的可靠性。这尤其是因为不同的平台可能具有不同的配置(例如进程数、缓存级别、核心数)和不同的负载级别。

我使用了threadtester,它是执行我的任务的绝佳工具:

    <dependency>
        <groupId>org.mapdb</groupId>
        <artifactId>thread-weaver</artifactId>
        <version>3.0.mapdb</version>
        <scope>test</scope>
    </dependency>

org.mapdb
织线工
3.0.mapdb
测试

如果不提供源代码,就很难判断如何导致并发问题。@NeplatnyUdaj补充道,“我想写一个测试来证明它不是线程安全的。”试图通过测试来证明(或反驳)任何代码段的线程安全性通常是徒劳的。问题是,即使是一小段代码,不同线程在内存上执行的存储和加载也可能有大量不同的方式交错。无论您使用什么编译器,无论您测试的是什么操作系统和硬件平台,如果您在任何其他系统、任何其他编译器上尝试,可能在任何其他一天,指令的交错方式都会有所不同。
    <dependency>
        <groupId>org.mapdb</groupId>
        <artifactId>thread-weaver</artifactId>
        <version>3.0.mapdb</version>
        <scope>test</scope>
    </dependency>