Модель памяти Java: переупорядочивание и одновременные блокировки - PullRequest
8 голосов
/ 05 апреля 2010

Модель java meomry требует, чтобы блоки synchronize, которые синхронизируются на одном и том же мониторе, обеспечивали преобразование переменных, измененных внутри этих блоков, до и после реализации. Пример:

// in thread A
synchronized( lock )
{
  x = true;
}

// in thread B
synchronized( lock )
{
  System.out.println( x );
}

В этом случае гарантируется, что поток B будет видеть x==true, пока поток A уже прошел этот synchronized -блок. Сейчас я нахожусь в процессе переписывания большого количества кода, чтобы использовать более гибкие (и, как говорят, более быстрые) блокировки в java.util.concurrent, особенно ReentrantReadWriteLock. Итак, пример выглядит так:

РЕДАКТИРОВАТЬ : пример был разбит, потому что я неправильно преобразовал код, как отмечено matt b . Исправлено следующим образом:

// in thread A
lock.writeLock().lock();
{
  x = true;
}
lock.writeLock().unlock();

// in thread B
lock.readLock().lock();
{
  System.out.println( x );
}
lock.readLock().unlock();

Однако в спецификации модели памяти я не видел никаких намеков на то, что такие блокировки также подразумевают необходимость упорядочения. Рассматривая реализацию, она, похоже, полагается на доступ к переменным переменным внутри AbstractQueuedSynchronizer (по крайней мере, для реализации sun). Однако это не является частью какой-либо спецификации, и, кроме того, доступ к энергонезависимым переменным в действительности не учитывается барьером памяти, заданным этими переменными, не так ли?

Итак, вот мои вопросы:

  • Безопасно ли принимать тот же порядок, что и со "старыми" synchronized блоками?
  • Это где-то задокументировано?
  • Доступ к любой изменчивой переменной является барьером памяти для любой другой переменной?

С уважением, Штеффен

-

Комментарий к Янамону:

Посмотрите на следующий код:

// in thread a
x = 1;
synchronized ( a ) { y = 2; }
z = 3;

// in thread b
System.out.println( x );
synchronized ( a ) { System.out.println( y ); }
System.out.println( z );

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

Ответы [ 4 ]

5 голосов
/ 05 апреля 2010

Из API-документа :

Все реализации Lock должны обеспечивать та же синхронизация памяти семантика, как предусмотрено встроенным блокировка монитора, как описано в Спецификация языка Java, третий Издание (17.4 Модель памяти):

* A successful lock operation has the same memory synchronization effects as a successful Lock action.
* A successful unlock operation has the same memory synchronization effects as a successful Unlock action.

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

4 голосов
/ 05 апреля 2010

Помимо вопроса о том, что гарантирует семантика модели памяти, я думаю, что есть несколько проблем с кодом, который вы публикуете.

  1. Вы синхронизируете дважды на одной и той же блокировке - это не нужно. При использовании реализации Lock вам не нужно использовать блок synchronized.
  2. Стандартная идиома использования Lock заключается в том, чтобы сделать это в блоке try-finally, чтобы предотвратить случайную разблокировку блокировки (поскольку блокировка не снимается автоматически при входе в какой-либо блок, в котором вы находитесь, как в synchronized блок).

Вы должны использовать Lock с чем-то похожим на:

lock.lock();
try {
    //do stuff
}
finally { 
    lock.unlock();
}
1 голос
/ 30 октября 2010

Yanamon, я не уверен, что вы правы - но по другим причинам, чем аргумент, который вы приводите.

Переменная unguardedVariable может быть переупорядочена в потоке "a" так, чтобы ее значение было установлено в 10 после того, как memoryBarrier установлено в true.

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

Параллелизм Java на практике, Брайан Гетц, стр. 34

ОБНОВЛЕНО: то, что я сказал, верно в случае старой модели памяти. Итак, если вы хотите написать «один раз - запустить где угодно», то мой аргумент в силе. Однако в новой модели памяти это не так, так как семантика, связанная с переупорядочением энергонезависимых переменных в присутствии энергозависимого доступа, стала более строгой (см. http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile).

1 голос
/ 05 апреля 2010

Чтение и запись изменчивых переменных теперь принудительно происходит раньше и происходит после упорядочения операций. Запись в переменную volatile имеет тот же эффект, что и освобождение монитора, а чтение переменной - как получение монитора. Следующий пример проясняет ситуацию:

volatile boolean memoryBarrier = false;
int unguardedValue = 0;

//thread a:
unguardedValue = 10;
memoryBarrier = true;

// thread b
if (memoryBarrier) {
  // unguardedValue is guaranteed to be read as 10;
}

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

  1. Использование Lock со встроенным ключевым словом Java syncronized эффективно делает доступ к блокировке уже однопоточным, поэтому Lock не дает возможности выполнить какую-либо реальную работу.
  2. Получение освобождения Lock должно быть сделано в соответствии с шаблоном ниже, это изложено в документах java Lock

lock.readLock().lock();
try {
  // Do work
} finally {
  lock.readLock.unlock();
}

...