2013-09-13 3 views
0

У меня был такой код:Нужно ли устанавливать флаги в синхронизированные блоки?

if(!flag) { 
    synchronized(lock) { 
    lock.wait(1000); 
    } 
} 
if(!flag) { print("Error flag not set!"); } 

И:

void f() { 
    flag = true; 
    synchronized(lock) { 
     lock.notify() 
    } 
} 

Мой друг сказал мне, что я должен поставить флаг = истинное внутри синхронизированного блока:

synchronized(lock) { 
     flag = true; 
     lock.notify() 
    } 

I не понимаю почему. Это какой-то классический пример? Может кто-нибудь, пожалуйста, объясните?

Если я объявляю свой флаг изменчивым, то мне не нужно вставлять его в синхронизированный блок?

+0

Не только это, но первый фрагмент кода должен всегда удерживать блокировку во время проверки флага. – user2357112

+0

, добавив к вышеуказанному комментарию, 'wait' должен находиться в цикле' while' – Pranalee

+0

Предлагаю вам прочитать [javadoc] (http://docs.oracle.com/javase/7/docs/api/java/lang/Object. html # wait% 28% 29), чтобы увидеть канонический шаблон, который вы должны использовать с 'wait'. – assylias

ответ

3

Как переменная flag используется несколькими потоками, необходим некоторый механизм для обеспечения видимости изменений. Это действительно общая картина в многопоточности в целом. Модель памяти Java иначе не гарантирует, что в другом потоке будет отображаться новое значение flag.

Это позволяет оптимизировать использование современных многопроцессорных систем, где сохранение когерентности кэша всегда может быть дорогостоящим. Доступ к памяти обычно является более медленным, чем другие «обычные» операции с процессором, поэтому современные процессоры идут на очень большие расстояния, избегая его как можно больше. Вместо этого часто используемые места хранятся в небольшой, быстрой локальной процессорной памяти - кеше. Изменения делаются только в кеше, а сбрасывает в основную память в определенные моменты. Это отлично подходит для одного процессора, так как содержимое памяти не изменяется другими сторонами, поэтому мы гарантируем, что содержимое кеша отражает содержимое памяти. (Ну, это просто упрощение, но с точки зрения программирования на высоком уровне, по моему мнению, не имеет значения). Проблема в том, что, как только мы добавляем другой процессор, независимо изменяя содержимое памяти, эта гарантия теряется. Чтобы смягчить эту проблему, были разработаны различные (иногда разработанные - см., Например, here) протоколы согласования кэш-памяти. Неудивительно, что для этого требуются некоторые служебные и межпроцессорные коммуникации.

Другие, некоторые из связанных вопросов atomicity операций записи. В принципе, даже если изменения видны другими потоками, можно увидеть частично. Обычно это не проблема в java, поскольку спецификация языка гарантирует атомарность всех записей. Тем не менее, пишет на 64-битные примитивов (long и double) явно указанных следует рассматривать как две отдельные, 32-битный пишет:

Для целей модели памяти языка программирования Java, одну запись на энергонезависимое длинное или двойное значение рассматривается как две отдельные записи: одна на каждую 32-битную половину. Это может привести к ситуации, когда поток видит первые 32 бита 64-битного значения из одной записи, а второй 32 бита из другой записи. (JLS 17.7)

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

2

Если вы проверяете и изменяете флаг из разных потоков, он должен быть объявлен как минимум volatile, чтобы потоки могли видеть изменения.

Смещение чеков в синхронизированных блоках тоже будет работать.

И да, это очень простая вещь в параллелизме, поэтому вы должны убедиться, что прочитали модель памяти, happens-before и другие связанные темы.

+0

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

3

Основная память медленная. Очень медленно. Внутренний кеш в вашем CPU сегодня примерно в 1000 раз быстрее. По этой причине современный код пытается сохранить как можно больше данных в кеше ЦП.

Одна из причин, почему основная память настолько медленная, так это то, что она является общей. Когда вы обновляете основную память, все ядра процессора уведомляются об этом изменении. Кэш, с другой стороны, относится к ядру. Это означает, что когда поток A обновляет флаг, он просто обновляет свой собственный кеш. Другие потоки могут видеть или не видеть изменения.

Есть два способа обеспечения флага записывается в основную память:

  1. Положите его в synchronized блоке
  2. Объявите это volatile

volatile имеет то преимущество, что любой доступ к флаг будет следить за обновлением состояния флага в основной памяти. Используйте это, когда вы используете флаг во многих местах.

В вашем случае у вас уже есть блок synchronized. Но в первом случае первый if может читать устаревшее значение (т. Е. Поток может быть wait(), хотя флаг уже равен true). Так что вам все еще нужно volatile.

+0

Итак, правильно ли объявить флаг неустойчивым? –

+0

@Alex Да, он обеспечивает правильную синхронизацию –

0

Прежде всего: lock.wait (1000) вернется через секунду, даже если другая нить не отправила уведомление.

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

В-третьих: пометить переменную флаг, как летучие, так разные темы убедитесь, что они всегда с помощью последней «написано» значение

И, наконец (! Флаг) Я хотел бы также поставить если код в синхронизированный блок - > он также получает доступ к переменной флага ...

+0

Если я делаю флаг volatile, мне все равно нужно поместить его в синхронизированный блок? –

+0

@Alex: Нет, одного из них достаточно. –

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