В каких ситуациях пустой синхронизированный блок может получить правильную семантику потоков? - PullRequest
24 голосов
/ 26 марта 2009

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

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

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

Ответы [ 5 ]

18 голосов
/ 26 марта 2009

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

11 голосов
/ 11 августа 2015

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

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


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

5 голосов
/ 26 марта 2009

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

4 голосов
/ 30 марта 2009

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

С http://www.javaperformancetuning.com/news/qotm030.shtml

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

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

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

...