Как проверить видимость значений между потоками - PullRequest
3 голосов
/ 12 сентября 2009

Как лучше всего проверить видимость значений между потоками?

class X {
 private volatile Object ref;

 public Object getRef() {
  return ref;
 }

 public void setRef(Object newRef) {
  this.ref = newRef;
 }
}

Класс X предоставляет ссылку на объект ref. Если параллельные потоки читают и записывают ссылку на объект, каждый поток должен видеть последний установленный объект. Модификатор volatile должен сделать это. Реализация здесь представляет собой пример, он также может быть синхронизирован или реализация на основе блокировки.

Теперь я ищу способ написать тест, который сообщит мне, когда значение видимости не соответствует указанному (более старые значения были прочитаны).

Ничего страшного, если тест сгорит несколько циклов процессора.

Ответы [ 5 ]

2 голосов
/ 12 сентября 2009

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

Я не вижу, что скажет вам предложенный вами тест. Если тест показывает несогласованные исполнения, он подтвердит правильность правильной синхронизации. Но если выполнение теста несколько раз показывает только (по-видимому) согласованные исполнения, это не говорит о том, что исполнения всегда будут согласованными.


Пример:

Предположим, что вы запускаете свои тесты на (скажем) JDK 1.4.2 (rev 12) / Linux / 32bit с «клиентской» JVM и опциями x, y, z, работающими на однопроцессорной машине. И что после выполнения теста 1000 раз вы заметите, что , кажется, не имеет никакого значения , если вы пропустите volatile. Что вы на самом деле узнали в этом сценарии?

  • Вы НЕ узнали, что это действительно не имеет значения? Если вы измените тест, чтобы использовать больше потоков и т. Д., Вы можете получить другой ответ. Если вы выполните тест еще несколько тысяч, миллионов или миллиардов раз, вы можете получить другой ответ.
  • Вы ничего не узнали о том, что может произойти на других платформах; например другая версия Java, другое оборудование или другие условия загрузки системы.
  • Вы НЕ узнали, является ли безопасным опускание ключевого слова volatile.

Вы узнаете что-то, только если тест показывает разницу. И единственное, что вы узнаете, это то, что синхронизация важна ... это то, о чем все учебники и т. Д. Говорили вам все это время: -)


Итог: это худший вид тестирования черного ящика. Это не дает вам реального понимания того, что происходит внутри коробки. Чтобы получить такое представление, вам необходимо: 1) понять модель памяти и 2) глубоко проанализировать собственный код, генерируемый JIT-компилятором (на нескольких платформах ...)

2 голосов
/ 12 сентября 2009

Если я правильно понимаю, вам нужен тестовый пример, который проходит, если переменная определена как volatile, и завершается неудачей, если нет.

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

Таким образом, модульный тест будет работать правильно, если указан volatile, но все равно может работать правильно без volatile.

1 голос
/ 12 сентября 2009

Ого, это намного сложнее, чем я думал. Я мог бы быть полностью выключен, но как насчет этого?

class Wrapper {
    private X x = new X();
    private volatile Object volatileRef;
    private final Object setterLock = new Object();
    private final Object getterLock = new Object();

    public Object getRef() {
        synchronized(getterLock) {
            Object refFromX = x.getRef();
            if (refFromX != volatileRef) {
                // FAILURE CASE!
            }
            return refFromX;
        }
    }

    public void setRef(Object ref) {
        synchronized(setterLock) {
            volatileRef = ref;
            x.setRef(ref);
        }
    }
}

Может ли это помочь? Конечно, вам придется создать много потоков, чтобы поразить эту оболочку, в надежде на появление плохого случая.

0 голосов
/ 13 сентября 2009

По сути, вам нужен такой сценарий: один поток записывает переменную, а другой читает ее одновременно, и вы хотите убедиться, что переменная read имеет правильное значение, верно?

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

Это займет некоторое время, потому что это не автоматизировано. Но я бы не стал писать модульный тест, который много раз пытается читать и писать, надеясь поймать тот случай, когда он потерпит неудачу, потому что вы ничего не добьетесь. Роль модульного теста заключается в том, чтобы гарантировать, что написанный вами код работает должным образом. Но в этом случае, если тест пройден, вы ни в чем не уверены. Может быть, это просто повезло, и конфликт не произошел на этом этапе. И это побеждает цель.

0 голосов
/ 12 сентября 2009

Как насчет этого?

public class XTest {

 @Test
 public void testRefIsVolatile() {
  Field field = null;
  try {
   field = X.class.getDeclaredField("ref");
  } catch (SecurityException e) {
   e.printStackTrace();
   Assert.fail(e.getMessage());
  } catch (NoSuchFieldException e) {
   e.printStackTrace();
   Assert.fail(e.getMessage());
  }
  Assert.assertNotNull("Ref field", field);
  Assert.assertTrue("Is Volatile", Modifier.isVolatile(field
    .getModifiers()));
 }

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...