Как проверить, что класс Java является потокобезопасным? - PullRequest
0 голосов
/ 21 марта 2019

Давайте возьмем этот простой класс:

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 и 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 является потокобезопасным.

Как написать модульный тест, который всегда будет проходить, когда класс является потокобезопасным?Как написать test, чтобы проверить, что метод get возвращает последнее установленное значение, даже если оно было изменено другим потоком?

1 Ответ

0 голосов
/ 21 марта 2019

Во-первых, ваш Counter класс бессмысленен.Метод updateCounter не обновляется, он возвращает новый объект.Так что просто удалите класс Counter и используйте long в вашем CounterService.

. Тогда остается вопрос, для чего предназначен CounterService.Это просто заворачивает долго.

Но не обращая на это внимания.Нет - вы не можете написать тест, чтобы доказать, что что-то не является потокобезопасным, поскольку проблемы многопоточности не являются детерминированными.Вы можете вставить задержки в места, где вы знаете, что могут возникнуть условия гонки, но это работает только в том случае, если вы уже знаете, что оно не является поточно-ориентированным в конкретном месте, и вы хотите это доказать.Но если вы не знаете, в чем проблема, вы не сможете вставить задержки в нужное место, чтобы доказать, что возможная проблема действительно существует.

К тому же, вы не сможете доказать, чтоэто также верно, хотя вы можете увеличить свои шансы, вставляя сны между операциями, чтобы вызвать проблемы.Но это может не сработать, и вы не тестируете реальный сценарий.

Ваш тест не пройден, потому что вы не понимаете, что делает синхронизированный и что подразумевает потокобезопасность.

В вашемtest, вы устанавливаете значение счетчика и затем получаете его в следующей строке.Если вы синхронизируете set и get, все это означает, что отдельные операции get и set являются поточно-ориентированными.Это не означает, что вы можете вызывать get и set отдельно, и этот get возвращает то же значение, что и предыдущий набор.

Если вы хотите установить что-то, а затем безопасно вернуть то же значение, у вас естьчтобы обернуть вызовы get и set в синхронизированный блок.

synchonized(this) { // get must return same thing that was set
    set
    get
}

Я настоятельно рекомендую вам сосредоточиться на понимании того, чего должна достичь ваша программа и что означает безопасность потоков и синхронизация в этом контексте.В противном случае вы все равно не сможете разработать правильный тест.

...