Подтвердить что ты не робот

Любые удовлетворительные подходы к обеспечению безопасности потоковой сборки в Java?

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

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

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

Любые предложения по инструментам/передовой практике/философии в целом приветствуются.

4b9b3361

Ответ 1

Забудьте о получении хороших результатов путем тестирования проблем concurrency. Попытайтесь уменьшить синхронизацию и уменьшите проблему. Затем используйте как можно большую библиотечную поддержку для синхронизации. И только если вы действительно попытаетесь обработать concurrency самостоятельно. Когда вы знаете, что каждый работник делает свою работу правильной, и все ваши мысли говорят вам, что у вас проблема с линией concurrency, а затем создайте интересную нагрузку. Рамки Unittest и их расширения могут выполнять эту работу, но знаете, что вы больше не тестируете какие-либо устройства. (Помните, что у вас уже была эта часть)

Если ваша модель concurrency получает сложные инструменты проверки, подходящие для этого, например SPIN.

Ответ 2

Java Concurrency на практике содержит отличную информацию о том, как писать тесты для Concurrency. Однако они не являются истинными модульными тестами. Почти невозможно записать истинный unit test для проблемы Concurrency.

В основном это сводится к этому. Создайте кучу тестовых потоков и запустите их. Каждый поток должен

  • Подождите, пока защелка счетчика
  • повторно вызывает некоторый метод, который модифицирует изменяемое состояние, о котором идет речь
  • обратный отсчет второй защелки и выход

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

Более того, чем другие типы ошибок, легче написать неудачный unit test для ошибки Concurrency после того, как вы нашли ошибку.

Ответ 3

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

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

public void testForThreadClash() {
  final CountDownLatch latch = new CountDownLatch(1);
  for (int i=0; i<50; ++i) {
    Runnable runner = new Runnable() {
      public void run() {
        try {
          latch.await();
          testMethod();
        } catch (InterruptedException ie) { }
      }
    }
    new Thread(runner, "TestThread"+i).start();
  }
  // all threads are waiting on the latch.
  latch.countDown(); // release the latch
  // all threads are now running concurrently.
}

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

Второй вопрос сложнее, но есть простое решение. Вопрос в следующем: откуда вы знаете, что ваш молот вызовет столкновение? Конечно, ваш код имеет синхронизированные блоки для предотвращения столкновения, поэтому вы не можете ответить на вопрос. Но вы можете. Просто удалите синхронизированные ключевые слова. Поскольку это синхронизированное ключевое слово, которое делает ваш класс потокобезопасным, вы можете просто удалить их и повторно запустить тест. Если тест действителен, он будет терпеть неудачу.

Когда я впервые написал код выше, он оказался недействительным. Я никогда не видел столкновения. Таким образом, это еще не действительный тест. Но теперь мы знаем, как получить четкий ответ на второй вопрос. Мы получаем неправильный ответ, но теперь мы можем возиться с тестом, чтобы генерировать неудачу, которую мы ищем. Вот что я сделал: я просто запускал тест 100 раз подряд.

for (int j=0; j<100; ++j) {
  testForThreadClash();
}

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

Ответ 4

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

Вот интересная статья (не знаю много о инструменте): http://today.java.net/pub/a/today/2003/08/06/multithreadedTests.html

Ответ 5

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

@Test
public void shouldDeliverMessage() throws Throwable {
  final Waiter waiter = new Waiter();

  messageBus.registerHandler(message -> {
    // Called on separate thread
    waiter.assertEquals(message, "foo");

    // Unblocks the waiter.await call
    waiter.resume();
  };

  messageBus.send("foo");

  // Wait for resume() to be called
  waiter.await(1000);
}

Ключевым моментом здесь является то, что любые неудачные утверждения в любом потоке будут повторно выбрасываться в основной поток с помощью waiter, позволяя тесту пройти или сбой, как он должен.