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

StackOverflow https://stackoverflow.com/questions/686415

Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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, и, следовательно, может зацикливаться вечно.

Сделайте изменение переменной видимым для всех потоков, используя empty synchronized блоки

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

int variable;

void test() // Corrected version
{
    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 синхронизируется-с все последующие действия по блокировке m" и тем самым случается -до эти действия.Итак, A разблокирует монитор o в хвостовой части своего synchronized происходит блокировка - до последующей блокировки B во главе своего блока.(Обратите внимание, именно этот странный порядок "хвост-начало" отношения объясняет, почему тела могут быть пустыми.) Учитывая также, что запись A предшествует ее разблокировке, а блокировка B предшествует ее чтению, отношение должно распространяться как на запись, так и на чтение: запись происходит -перед чтением.Именно это решающее, расширенное соотношение делает пересмотренную программу корректной с точки зрения модели памяти.

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


   * Я говорю так, как если бы это был вопрос кэширования процессора потому что я думаю, что это полезный способ просмотра.По правде говоря, как прокомментировал Александр Дубинский, ‘все современные процессоры поддерживают кэширование.Отношение "произойдет до" больше относится к тому, что разрешено делать компилятору, а не центральному процессору.’

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

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

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

  1. Поток получает блокировку на мониторе для объекта this (предполагая, что монитор разблокирован, в противном случае поток ожидает, пока монитор не будет разблокирован).
  2. Память потока очищает все его переменные, т. е.все его переменные эффективно считываются из "основной" памяти (JVM могут использовать грязные наборы для оптимизации этого, чтобы сбрасывались только "грязные" переменные, но концептуально это одно и то же.Смотрите раздел 17.9 спецификации языка Java).
  3. Блок кода выполняется (в данном случае устанавливается возвращаемое значение равным текущему значению i3, которое, возможно, только что было сброшено из "основной" памяти).
  4. (Любые изменения в переменных обычно теперь записываются в "основную" память, но для geti3() у нас нет изменений.)
  5. Поток снимает блокировку с монитора для объекта this.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top