让我们上这个简单的课:
public class CounterService {
private volatile Counter counter;
public CounterService(Counter counter) {
this.counter = counter;
}
public long getCounterValue() {
System.out.println("GET: " + this.counter.counter + " in thread " +
Thread.currentThread().getName());
return this.counter.counter;
}
public long setCounterValue(long newValue) {
this.counter = this.counter.updateCounter(newValue);
System.out.println("--set: " + newValue + " in thread " +
Thread.currentThread().getName());
return this.counter.counter;
}
}
public class Counter {
public final long counter;
public Counter(long counter) {
this.counter = counter;
}
public Counter updateCounter(long i) {
return new Counter(i);
}
}
现在,我想编写单元测试,如果CounterService
是线程安全的(例如,当我设置get
和{{1}时),它将始终通过}方法同步)。我相信编写测试可能是不可能的,如果该类不是线程安全的,那么它将始终失败。
我尝试过这样的事情:
set
但是即使@Test
public void multipleThreadSetAndGetShouldCorrectValue() throws ExecutionException, InterruptedException {
int threads = 10;
final Counter counter = new Counter(0);
final CounterService counterService = new CounterService(counter);
CountDownLatch latch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(threads);
Collection<Future<Long>> results = new ArrayList<>();
AtomicLong sequence = new AtomicLong(0);
for (int i = 0; i < threads; i++) {
results.add(executorService.submit(() -> {
latch.await(1, TimeUnit.SECONDS);
latch.countDown();
counterService.setCounterValue(sequence.getAndIncrement());
return counterService.getCounterValue();
}));
}
final Set<Long> uniqueResult = new HashSet<>();
for (Future<Long> result : results) {
uniqueResult.add(result.get());
}
assertEquals(threads, uniqueResult.size());
}
是线程安全的,该测试有时也会失败。
如何编写在类是线程安全的情况下始终通过的单元测试?如何编写测试以检查CounterService
方法是否返回了最后的设置值,即使该值已被另一个线程修改了?
答案 0 :(得分:0)
首先,您的Counter
类毫无意义。 updateCounter
方法不会更新,它将返回一个新对象。因此,只需删除Counter
类并在long
中使用CounterService
。
然后还有一个问题,CounterService
是做什么用的。它只是包裹了很长的时间。
但是不管那个。不,您不能真正编写测试来证明某些内容不是线程安全的,因为多线程问题不是确定性的。您可以将延迟插入可能会出现竞争条件的地方,但这仅在您已经知道特定位置不是线程安全并且想要证明这一点的情况下才有效。但是,如果您不知道问题出在哪里,则可能无法在适当的位置插入延迟,以证明确实存在问题。
使用相同的标记,您也无法真正证明它是正确的,尽管您可以通过在两次操作之间插入睡眠以强制出现问题来增加机会,尽管如此。但这可能无法正常工作,您也没有测试实际情况。
您的测试失败,因为您不了解同步的含义和线程安全的含义。
在测试中,您要设置一个计数器值,然后在下一行获取它。如果将set和get进行同步,则所有操作意味着线程的get和set操作是线程安全的。这并不意味着您可以分别调用get和set,并且get将返回与上一个set相同的值。
如果要设置一些内容然后安全地重新获得相同的值,则必须将get和set调用包装在一个同步块中。
synchonized(this) { // get must return same thing that was set
set
get
}
我强烈建议您专注于理解程序需要实现的内容以及在这种情况下线程安全性和同步化的含义。否则,您将无法开发正确的测试。