Статьи

Пять способов синхронизации многопоточных интеграционных тестов

Несколько недель назад я написал блог о синхронизации многопоточных интеграционных тестов, который был переиздан на DZone Javalobby, где он получил комментарий от Роберта Сольнье, который совершенно справедливо указал, что вы также можете использовать join () для синхронизации рабочего потока и его модуля. тесты. Это заставило меня задуматься о том, сколько способов вы можете синхронизировать многопоточные интеграционные тесты? Итак, я начал считать …и придумал:

  1. Использование случайной задержки.
  2. Добавление CountDownLatch
  3. Thread.join ()
  4. Приобретение семафора
  5. С Будущим и ExecutorService

Теперь я не буду подробно объяснять все следующее, я позволю коду говорить за себя, за исключением того, что все примеры кода делают примерно одно и то же: модульный тест создает экземпляр ThreadWrapper и затем вызывает его метод doWork () (или вызов () в случае будущего). Затем основной поток модульного теста ожидает завершения рабочего потока, прежде чем подтвердить, что тест пройден.

Для примера кода, демонстрирующего пункты 1 и 2, взгляните на мой оригинальный блог о
синхронизации многопоточных интеграционных тестов , хотя я бы не рекомендовал пункт 1: использовать случайную задержку.

Thread.join ()

public class ThreadWrapper {

  private Thread thread;

  /**
  * Start the thread running so that it does some work.
  */
  public void doWork() {

  thread = new Thread() {

  /**
  * Run method adding data to a fictitious database
  */
  @Override
  public void run() {

  System.out.println("Start of the thread");
  addDataToDB();
  System.out.println("End of the thread method");
  }

  private void addDataToDB() {

  try {
  Thread.sleep(4000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  };

  thread.start();
  System.out.println("Off and running...");
  }

  /**
  * Synchronization method.
  */
  public void join() {

  try {
  thread.join();
  } catch (InterruptedException ex) {
  Thread.currentThread().interrupt();
  }
  }
}
public class ThreadWrapperTest {

  @Test
  public void testDoWork() throws InterruptedException {

  ThreadWrapper instance = new ThreadWrapper();

  instance.doWork();
  instance.join();

  boolean result = getResultFromDatabase();
  assertTrue(result);
  }

  /**
  * Dummy database method - just return true
  */
  private boolean getResultFromDatabase() {
  return true;
  }
}

Приобретение семафора

public class ThreadWrapper {

  /**
  * Start the thread running so that it does some work.
  */
  public void doWork() {
  doWork(null);
  }

  @VisibleForTesting
  void doWork(final Semaphore semaphore) {

  Thread thread = new Thread() {

  /**
  * Run method adding data to a fictitious database
  */
  @Override
  public void run() {

  System.out.println("Start of the thread");
  addDataToDB();
  System.out.println("End of the thread method");
  semaphore.release();
  }

  private void addDataToDB() {

  try {
  Thread.sleep(4000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  };

  aquire(semaphore);
  thread.start();
  System.out.println("Off and running...");
  }

  private void aquire(Semaphore semaphore) {
  try {
  semaphore.acquire();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
}
public class ThreadWrapperTest {

  @Test
  public void testDoWork() throws InterruptedException {

  ThreadWrapper instance = new ThreadWrapper();

  Semaphore semaphore = new Semaphore(1);
  instance.doWork(semaphore);
  semaphore.acquire();

  boolean result = getResultFromDatabase();
  assertTrue(result);
  }

  /**
  * Dummy database method - just return true
  */
  private boolean getResultFromDatabase() {
  return true;
  }
}

С будущим

public class ThreadWrapper implements Callable<Boolean> {

  @Override
  public Boolean call() throws Exception {
  System.out.println("Start of the thread");
  Boolean added = addDataToDB();
  System.out.println("End of the thread method");
  return added;
  }

  /**
  * Add to the DB and return true if added okay
  */
  private Boolean addDataToDB() {

  try {
  Thread.sleep(4000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  return Boolean.valueOf(true);
  }
}
public class ThreadWrapperTest {

  @Test
  public void testCall() throws ExecutionException, InterruptedException {

  ThreadWrapper instance = new ThreadWrapper();

  ExecutorService executorService = Executors.newFixedThreadPool(1);

  Future<Boolean> future = executorService.submit(instance);

  Boolean result = future.get();

  assertTrue(result);
  }
}

Перечислив все эти методы, следующее, что нужно рассмотреть, какой из них лучше? Задавая этот вопрос, вы должны определить слово «лучший» с точки зрения
лучшего для чего? Лучше всего для простоты? Ремонтопригодность? Скорость или размер кода? После всего
программирования искусство принятия правильного решения . Возможно, вы уже догадались, что мне не нравится идея случайной задержки и я предпочитаю использовать CountDownLatch. Thread.join () немного старая школа; помните, что подобные Semaphore и CountDownLatch были написаны для улучшения оригинальных Java-потоков. ExecutorService кажется немного тяжелым для того, что нам нужно здесь. В конце концов, выбор техники действительно сводится к личным предпочтениям.

Наконец, я перечислил пять способов синхронизации многопоточных интеграционных тестов; Однако, если вы можете думать о других, пожалуйста, дайте мне знать …


Исходный код этого блога доступен на
Github .