假设我有这样一个方法:
synchronized void incrementIndex() {
index++;
}
我想对这个方法进行单元测试,以查看如果多个线程同时尝试递增索引,索引的最终值是否被正确设置。假设我不知道该方法声明中的 “synchronized” 关键字(我只知道该方法的契约),我该如何进行测试?
p.s. 如果有帮助的话,我正在使用 Mockito 编写测试用例。
假设我有这样一个方法:
synchronized void incrementIndex() {
index++;
}
我想对这个方法进行单元测试,以查看如果多个线程同时尝试递增索引,索引的最终值是否被正确设置。假设我不知道该方法声明中的 “synchronized” 关键字(我只知道该方法的契约),我该如何进行测试?
p.s. 如果有帮助的话,我正在使用 Mockito 编写测试用例。
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class SyncTest {
private final static int NUM_THREADS = 10;
private final static int NUM_ITERATIONS = 1000;
@Test
public void testSynchronized() throws InterruptedException {
// This test will likely perform differently on different platforms.
ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
final Counter sync = new Counter();
final Counter notSync = new Counter();
for (int i = 0; i < NUM_THREADS; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < NUM_ITERATIONS; i++) {
sync.incSync();
notSync.inc();
}
}
});
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
assertThat(sync.getValue(), is(NUM_THREADS * NUM_ITERATIONS));
assertThat(notSync.getValue(), is(not(NUM_THREADS * NUM_ITERATIONS)));
}
@Test
public void methodIncSyncHasSynchronizedModifier() throws Exception {
Method m = Counter.class.getMethod("incSync");
assertThat(Modifier.isSynchronized(m.getModifiers()), is(true));
}
private static class Counter {
private int value = 0;
public synchronized void incSync() {
value++;
}
public void inc() {
value++;
}
public int getValue() {
return value;
}
}
}
CandiedOrange在他对你的问题的评论中是正确的。换句话说,鉴于你提到的方法,你不必担心threadA在threadB调用该方法的同时调用该方法,因为两个调用都会写入index。如果是这样的情况:
void incrementIndex() {
index++;
System.out.println(index); // threadB might have written to index
// before this statement is executed in threadA
}
当threaA调用该方法时,它会在第一条语句中增加index值,然后尝试在第二条语句中读取index的值,此时threadB可能已经调用该方法并在threadA读取并打印之前增加了index。这就是需要使用synchronized
以避免出现这种情况。
现在,如果您仍想测试同步,并且可以访问方法代码(或者可能可以做类似的原型),您可以考虑以下示例,其中演示多线程如何处理带有同步方法的代码:
public void theMethod(long value, String caller) {
System.out.println("thread" + caller + " is calling...");
System.out.println("thread" + caller + " is going to sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread" + caller + " woke up!");
}
这应该输出:
threadA is calling...
threadA is going to sleep...
threadA woke up!
threadB is calling...
threadB is going to sleep...
threadB woke up!
synchronized
关键字,输出将会是:threadA is calling...
threadA is going to sleep...
threadB is calling...
threadB is going to sleep...
threadA woke up!
threadB woke up!
是的。i++是原子操作吗?
不是。
因此,如果您关心程序的正确性,则同步是合理的。
但测试很难。
视觉检查告诉我们,非原子增量操作受到保护并变为原子操作,就我们所知一切都很好,但我们对系统的其余状态一无所知。
可以通过副作用来测试函数是否同步。有一种可测试的模式,可以组织代码,使您注入同步而不是使用Java内置功能,但如果只有您最初的问题,那么我会依靠视觉检查和明显的正确性。