Подтвердить что ты не робот

В каких ситуациях пустой синхронизированный блок может получить правильную семантику потоков?

Я просмотрел отчет Findbugs на моей базе кода, и один из шаблонов, который был запущен, был для пустого synchronzied (т.е. synchronized (var) {}). Документация сообщает:

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

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

4b9b3361

Ответ 1

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

Ответ 2

В более ранних ответах не подчеркивается самая полезная вещь о пустых блоках synchronized: они могут обеспечить видимость изменений переменных и других действий по потокам. Как указывает jtahlborn, синхронизация накладывает на компилятор "барьер памяти", который заставляет его скрывать и обновлять свои кеши. Но я не нашел, где "SnakE обсуждает" это, поэтому я сам написал ответ.

int variable;

void test() // this code is INCORRECT
{
    new Thread( () ->  // A
    {
        variable = 9;
        for( ;; )
        {
            // do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

Вышеуказанная программа неверна. Значение переменной может быть кэшировано локально в потоке A или B или обоих. Таким образом, B никогда не сможет прочитать значение 9, которое пишет A, и поэтому может зацикливаться навсегда.

Сделать переменное изменение видимым в потоках с помощью пустых synchronized блоков

Одна из возможных поправок заключается в добавлении к переменной модификатора volatile (эффективно "без кэша" ). Иногда это неэффективно, потому что оно полностью запрещает кеширование переменной. Пустые блоки synchronized, с другой стороны, не запрещают кеширование. Все, что они делают, это заставить тайники синхронизироваться с основной памятью в определенных критических точках. Например:

int variable;

void test() // revised
{
    new Thread( () ->  // A
    {
        variable = 9;
        synchronized( o ) {} // flush to main memory
        for( ;; )
        {
            // do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            synchronized( o ) {} // refresh from main memory
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

final Object o = new Object();

Как модель памяти гарантирует видимость

Оба потока должны синхронизироваться на одном и том же объекте, чтобы гарантировать видимость. Эта гарантия основана на модели памяти Java, в частности на правиле, которое "действие разблокировки на мониторе m синхронизируется со всеми последующими действиями блокировки на м" и тем самым происходит - до этих действий. Таким образом, разблокировка o-монитора в хвосте блока synchronized происходит - до B последующего блокирования в начале его блока. (Обратите внимание, что это странный хвостовой порядок отношения, объясняющий, почему тела могут быть пустыми.) Учитывая также, что запись A предшествует ее разблокировке, а блокировка B предшествует ее чтению, отношение должно распространяться на покрытие как записи, так и чтения: write бывает - перед чтением. Это ключевое, расширенное отношение, которое делает исправленную программу правильной с точки зрения модели памяти.

Более глубокий эффект, чем летучий

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

Я думаю, что это наиболее важные применения для пустых блоков synchronized.

Ответ 3

Раньше было, что спецификация подразумевала определенные операции с защитой памяти. Однако спецификация теперь изменилась, и исходная спецификация никогда не была выполнена правильно. Он может использоваться, чтобы ждать, пока другой поток освободит блокировку, но координирование того, что другой поток уже приобрел блокировку, будет сложным.

Ответ 4

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

От http://www.javaperformancetuning.com/news/qotm030.shtml

  • Нить получает блокировку на мониторе для объекта (при условии, что монитор разблокирован, иначе поток ждет, пока монитор не разблокируется).
  • В памяти потоков сбрасываются все его переменные, т.е. все его переменные эффективно считываются из "основной" памяти (JVM могут использовать грязные наборы, чтобы оптимизировать это, чтобы очистить только "грязные" переменные, но концептуально это то же самое См. Раздел 17.9 спецификации языка Java).
  • Выполняется блок кода (в этом случае устанавливается значение возврата к текущему значению i3, которое могло быть только reset из основной памяти).
  • (Любые изменения переменных обычно записываются в "главную" память, но для geti3() у нас нет изменений.)
  • Этот поток освобождает блокировку на мониторе для этого объекта.

Ответ 5

Для углубленного изучения модели памяти Java ознакомьтесь с этим видео из серии "Расширенные темы в языках программирования Google": http://www.youtube.com/watch?v=1FX4zco0ziY

Он дает действительно хороший обзор того, что компилятор может (часто в теории, но иногда и на практике) делать с вашим кодом. Существенный материал для любого серьезного Java-программиста!