以下代码在Thread中增加一个静态变量,并检查其值是否增加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);
}
}
}
更新:从格雷的回答中我更新了代码,当我删除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方法会导致测试通过
答案 0 :(得分: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();
}
答案 1 :(得分:0)
答案就在于问题本身。这是一个竞争条件:
无论多少次尝试抛出线程或尝试运行它的次数多少,都无法保证它会发生。这就是为什么这是一场竞争条件。这是non-deterministic
假设统一的概率分布甚至远远不正确,除非你能说明原因。你不是在这里翻硬币。代码可以在比赛暴露之前运行几个月,我已经看到这种情况多次发生。这就是为什么竞争条件很难解决而且很重要的原因。
其次,您不会在场景中播种任何数量的随机噪音。如果你说每个线程的运行函数首先睡了一段随机的时间,这样它们实际上可能会相互重合,这会更有趣......但你的线程很短,它们可能已经完成,甚至从未运行过与生成调度作业所花费的时间相比,并行。
答案 2 :(得分:0)
首先,正如其他答案所指出的那样,种族无法保证发生。删除sysout
语句,因为这可能导致代码同步。