2010-04-05 2 views
8

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

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

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

В этом случае она Гарантированная, что поток В будет видеть x==true тех пор, пока нить А уже прошел, что synchronized -блока. Теперь я перерабатываю много кода, чтобы использовать более гибкие (и считающиеся более быстрыми) блокировки в java.util.concurrent, особенно ReentrantReadWriteLock. Так пример выглядит следующим образом:

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

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

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

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

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

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

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

-

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

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

// 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, но не оказывает гарантированного влияния на другие переменные ...? Итак, как это можно сравнить с доступом к изменчивой переменной?

+0

Одна заметка о добавленном вами коде, поток b будет печатать только 2, если он получает блокировку перед потоком a ... это было своего рода подразумеваемым, но я просто хотел сделать это понятным. Но для ответа на ваш неустойчивый вопрос, volatile будет использоваться следующим образом для обеспечения видимости: -------- volatile boolean memoryBarrier = false; int unguardedValue = 0; // thread a: unguardedValue = 10; memoryBarrier = true; // thread b if (memoryBarrier) { // unguardedValue гарантированно считается 10; } – Yanamon

+0

Ну, я думаю, что писать код в комментариях не очень хорошо, я обновил свой ответ на примере – Yanamon

ответ

5

От API-doc:

реализация

Всех блокировок должна обеспечивать ту же синхронизацию ПАМЯТИ семантику, как это предусмотрено встроенного блокировки монитора, как описано в спецификации языка 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. 

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

+0

Вы абсолютно правы. Я читал много вещей, но я, кажется, полностью пропустил интерфейс Lock. –

+0

Можете ли вы прокомментировать изменчивый вопрос? –

+0

@Steffen Heil: если я правильно его помню, любой доступ к изменчивой переменной имеет только синхронизирующий эффект с другими обращениями к одной и той же переменной, то есть не гарантируется какой-то общий барьер памяти. Быстрое сканирование JLS, похоже, подтверждает это воспоминание. Но возьмите это с огромным количеством соли, поскольку прошло довольно много времени с тех пор, как я в последний раз столкнулся с моделью памяти и ее последствиями ... – Dirk

4

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

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

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

lock.lock(); 
try { 
    //do stuff 
} 
finally { 
    lock.unlock(); 
} 
+0

Вы правы, мой пример был сломан. Я задал вопрос. И да, я обычно использую try/finally, просто оставляя его здесь для краткости. –

1

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

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 со встроенным в syncronized ключевом слове эффективно делает доступ в Java-к замку уже однопоточный, так что не дают Lock шанса сделать любую реальную работу.
  2. приобретя высвобождая Lock должны быть сделано следующим ниже шаблона, это изложенное в ява документации по Lock

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

+0

См. Комментарий в вопросе, пожалуйста. –

+0

Я понимаю, что нить b не увидит unguardedValue, потому что упорядочение не влияет на видимость, а unguardedValue не является изменчивым, поэтому не обязательно видимым потоком b. Это правильно ? –

1

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

Неохраняемая переменная переменная может быть переупорядочена в потоке «a», так что ее значение установлено равным 10 после memoryBarrier установлен в значение true.

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

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

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

Смежные вопросы