我有这个 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()是同步的。谢谢你的帮助
这一切都过去了,但问题仍然存在,只是发生的可能性较小。假设两个线程运行相同的代码,
tmp
是局部变量,对于堆栈上的每个线程都是唯一的,而private int count
是共享的,驻留在堆中。如果两个线程同时运行,它们可以将相同的值复制到 tmp
,假设是 0,然后运行 count = tmp + 1;
,每次都会存储值 1。
使用
count++;
在幕后可能会发生同样的事情,在处理器上它仍然是一个复杂的非原子操作,只是不太容易发生,因为关键部分现在是count++
并且两个线程不太可能会同时通过的。