1

下面的代码在线程中增加一个静态变量,并检查它的值是否增加了 1。这是Assert.assertEquals(currentAVal+1, accessCounter);检查。

测试始终通过 10'000 次运行。但是为什么没有竞争条件导致测试失败?我希望在断言发生之前有两个或更多线程accessCounter在行增加,accessCounter = accessCounter + 1;但这似乎没有发生?

public class RunnableTest {
    private static int accessCounter = 0;

    private class Post implements Runnable {
        public void run() {
            int currentAVal = accessCounter;
            accessCounter = accessCounter + 1;
            Assert.assertEquals(currentAVal+1, accessCounter);
            System.out.println("Access counter : "+accessCounter);
        }
    }

    @Test
    public void runTest(){
        Runnable r = new Post();    
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
        for(int executorCount = 0; executorCount < 10000; ++executorCount) {  
            executor.execute(r);
        }
    }
}

更新:从 Gray 的回答中,我更新了代码,当我删除 println 语句时,我现在收到一个竞争条件(测试失败):

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import junit.framework.Assert;

public class RunnableTest {

    private static int accessCounter = 0;
    private static List<String> li = new ArrayList<String>();

    private class Post implements Runnable {
        public synchronized void run() {

            int currentAVal = accessCounter;
            accessCounter = accessCounter + 1;
            li.add(String.valueOf(currentAVal+1+","+accessCounter));

        }
    }

    @Test
    public void runTest(){

        Runnable r = new Post();    
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
        for(int executorCount = 0; executorCount < 10000; ++executorCount) {  
            executor.execute(r);
        }
        //Wait for threads to finish
        // we shut it down once we've submitted all jobs to it
        executor.shutdown();
        // now we wait for all of those jobs to finish
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(String s : li){
            Assert.assertEquals(s.split(",")[0], s.split(",")[1]);
        }
    }

}

添加synchronized到 run 方法会导致测试通过

4

3 回答 3

5

测试始终通过 10'000 次运行。但是为什么没有竞争条件导致测试失败?

竞态条件的定义是您可能会遇到时间问题——这是不能保证的。如果你在另一个架构上运行它,你可能会得到截然不同的结果。

但是,我认为junit不会看到其他线程中的断言。例如,如果我更改您测试以下内容。我确实看到值不同但fail测试方法没有看到 - 测试仍然通过。

if (currentAVal+1 != accessCounter) {
    System.out.println("Access counter not equal: "+accessCounter);
    Assert.fail();
}

您可能会看到正确值的一个原因accessCounter是这System.out.println(...)是一种同步方法,它(作为副产品)同步accessCounter.

此外,您没有关闭执行程序,也没有等待执行程序服务实际完成。您应该执行以下操作:

// we shut it down once we've submitted all jobs to it
executor.shutdown();
// now we wait for all of those jobs to finish
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

但这并不能解决其他线程问题。要实际查看线程的结果,您可以执行以下操作:

List<Future<?>> futures = new ArrayList<Future<?>>();
for (int executorCount = 0; executorCount < 10000; ++executorCount) {
    futures.add(executor.submit(r));
}
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
for (Future<?> future : futures) {
    // this will throw an exception if an assert happened
    future.get();
}
于 2013-08-30T20:47:11.107 回答
0

答案就在问题本身。这是一种竞争条件:
无论您尝试向它抛出多少次线程或尝试运行它多少次,您都无法保证它永远不会发生。这就是为什么它是一个竞争条件。它是non-deterministic

除非您能说明原因,否则假设对此的均匀概率分布甚至都不正确。你不是在这里抛硬币。代码可能会在比赛公开之前运行几个月,我已经看到这种事情发生了很多次。这就是为什么竞争条件难以解决且预防很重要的原因。

其次,您不会在场景中植入任何数量的随机噪声。如果您说让每个线程的运行函数先随机休眠一段时间,以便它们实际上可能彼此重合,这将更有趣……但是您的线程太短了,它们很可能已经完成并且甚至从未运行与生成调度作业所需的时间相比。

于 2013-08-30T20:46:18.010 回答
0

首先,正如其他答案所指出的那样,不能保证Race发生。删除该sysout语句,因为这会导致代码同步。

于 2013-08-30T20:51:30.530 回答