Java 并发性和失败的 JUnit 测试

问题描述 投票:0回答:1

我有这个 JUnit 测试:

package com.baeldung.concurrent;

import static org.junit.Assert.assertEquals;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

/**
 * This is defined as a manual test because it tries to simulate the race conditions
 * in a concurrent program that is poorly designed and hence may fail nondeterministically.
 * This will help the CI jobs to ignore these tests and a developer to run them manually.
 *
 */
public class MyCounterSimpleManualTest {

    @Test
    public void testCounter() {
        MyCounter counter = new MyCounter();
        for (int i = 0; i < 500; i++)
            counter.increment();
        assertEquals(500, counter.getCount());
    }

    @Test
    public void testCounterWithConcurrency() throws InterruptedException {
        int numberOfThreads = 100;
        ExecutorService service = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        MyCounter counter = new MyCounter();
        for (int i = 0; i < numberOfThreads; i++) {
            service.execute(() -> {
                counter.increment();
                latch.countDown();
            });
        }
        latch.await();
        assertEquals(numberOfThreads, counter.getCount());
    }

    @Test
    public void testSummationWithConcurrencyAndWait() throws InterruptedException {
        int numberOfThreads = 2;
        ExecutorService service = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        MyCounter counter = new MyCounter();
        for (int i = 0; i < numberOfThreads; i++) {
            service.submit(() -> {
                try {
                    counter.incrementWithWait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            });
        }
        latch.await();
        assertEquals(numberOfThreads, counter.getCount());
    }

}

这是我的 MyCounter(版本 1):

包 com.baeldung.concurrent;

public class MyCounter {

    private int count;

    public void increment() {
        int tmp = count;
        count = tmp + 1;
    }

    public synchronized void incrementWithWait() throws InterruptedException {
        int tmp = count;
        wait(100);
        count = tmp + 1;
    }

    public int getCount() {
        return count;
    }

}

这是 JUnit 失败的堆栈跟踪:

 java.lang.AssertionError: expected:<2> but was:<1>
        at org.junit.Assert.fail(Assert.java:89)
        at org.junit.Assert.failNotEquals(Assert.java:835)
        at org.junit.Assert.assertEquals(Assert.java:647)
        at org.junit.Assert.assertEquals(Assert.java:633)
        at com.baeldung.concurrent.MyCounterSimpleManualTest.testSummationWithConcurrencyAndWait(MyCounterSimpleManualTest.java:60)

当我将 MyCounter 更改为此时,JUnit 可以工作(版本 2):

package com.baeldung.concurrent;

public class MyCounter {

    private int count;

    public void increment() {
        count++;
    }

    public synchronized void incrementWithWait() throws InterruptedException {
        wait(100);
        count++;
    }

    public int getCount() {
        return count;
    }

}    

有人可以解释一下为什么 MyCounter 的第一个版本不起作用。也许存在一些竞争条件问题,但我不明白为什么,因为方法incrementWithWait()是同步的。谢谢你的帮助

java concurrency
1个回答
0
投票

这一切都过去了,但问题仍然存在,只是发生的可能性较小。假设两个线程运行相同的代码,

tmp
是局部变量,对于堆栈上的每个线程都是唯一的,而
private int count
是共享的,驻留在堆中。如果两个线程同时运行,它们可以将相同的值复制到
tmp
,假设是 0,然后运行
count = tmp + 1;
,每次都会存储值 1。

使用

count++;
在幕后可能会发生同样的事情,在处理器上它仍然是一个复杂的非原子操作,只是不太容易发生,因为关键部分现在是
count++
并且两个线程不太可能会同时通过的。

© www.soinside.com 2019 - 2024. All rights reserved.