2016-05-10 4 views
52

В следующем коде:Почему `synchronized (new Object()) {}` a no-op?

class A { 
    private int number; 

    public void a() { 
     number = 5; 
    } 

    public void b() { 
     while(number == 0) { 
      // ... 
     } 
    } 
} 

Если метод Ь называется, а затем новый поток запускается, который запускает метод а, то метод б не гарантируется когда-либо увидеть изменение number и, таким образом, b никогда не может прекратить ,

Конечно, мы можем сделать numbervolatile, чтобы решить эту проблему. Однако по академическим причинам давайте предположим, что volatile не вариант:

JSR-133 FAQs говорит нам:

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

Это звучит, как мне просто нужно как a и b, чтобы войти и выйти из любого synchronized -блока вообще, независимо от того, что они используют монитор. Точнее, это звучит, как это ...:

class A { 
    private int number; 

    public void a() { 
     number = 5; 
     synchronized(new Object()) {} 
    } 

    public void b() { 
     while(number == 0) { 
      // ... 
      synchronized(new Object()) {} 
     } 
    } 
} 

... устранит проблему и будет гарантировать, что b будет видеть изменения в a и, таким образом, также в конце концов прекращается.

Однако часто задаваемые вопросы также четко указано:

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

synchronized (new Object()) {} 

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

Теперь это сбивает с толку. Я думал, что синхронизированный оператор вызовет сброс кешей. Конечно, он не может очистить кеш от основной памяти так, что изменения в основной памяти могут отображаться только потоками, которые синхронизируются на одном мониторе, тем более что для volatile, который в основном делает то же самое, нам даже не нужно монитор, или я ошибаюсь там? Итак, почему это не-op и не вызывает b для прекращения гарантии?

+2

Ссылка не сохраняется, поэтому ни один другой поток не может ждать этой ссылки. Что вы пытаетесь защитить? –

+14

Этот вопрос прекрасно иллюстрирует причину, по которой я призываю людей не пытаться объяснить или понять семантику языка с точки зрения реализации конкретных вещей, которые могут или не могут существовать, такие как вымышленный «кеш локального процессора». –

+1

@DavidSchwartz Точно, и на самом деле фраза «очистка кеша», особенно когда в непосредственной близости от фразы «основная память», может быть очень обманчивой относительно того, что на самом деле происходит. Протоколы когерентности кэша часто обеспечивают согласованность памяти, фактически не доходя до медленной основной памяти. И если вы откажетесь от JLS и JMM и попробуете рассуждать о базовой архитектуре, вам нужно учитывать такие вещи, как тот факт, что некоторые механизмы синхронизации не обеспечивают согласованность глобальной памяти (продемонстрировано IRIW). Итак, как сказал ишавит, придерживаться JLS - путь. –

ответ

49

Вопросы и ответы не являются полномочиями по этому вопросу; JLS. Раздел 17.4.4 определяет синхронизацию - с отношениями, которые передаются в отношения -отношения (17.4.5). Соответствующая точка пули:

  • Действие разблокировки на мониторе мсинхронизирует-с всех последующих действий замок на м (где «последующее» определяется в соответствии с порядком синхронизации).

С м здесь ссылка на new Object(), и он никогда не хранятся или опубликованы в любой другой поток, мы можем быть уверены, что никакой другой поток не получит блокировку на м после блокировки в этом блоке. Кроме того, поскольку m - новый объект, мы можем быть уверены, что никаких действий, которые ранее были разблокированы, не было. Поэтому мы можем быть уверены, что никакое действие формально не синхронизируется - с этим действием.

Технически вам даже не нужно делать полный кеш-флеш, чтобы соответствовать спецификации JLS; это больше, чем требует JLS. A типичный реализация делает это, потому что это самая простая вещь, которую позволяет аппаратное обеспечение, но это происходит «выше и дальше», так сказать. В случаях, когда escape analysis сообщает оптимизирующему компилятору, что нам нужно еще меньше, компилятор может выполнять меньше. В вашем примере анализ escape-кода может сообщить компилятору, что действие не имеет эффекта (из-за рассуждений выше) и может быть полностью оптимизировано.

+5

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

+0

Да, вы поняли. – yshavit

+2

@yankee. В чем часто говорится в FAQ, неуклюже и глупо, IMO, заключается в том, что если бы существовал такой кеш, и очистка этого кеша была необходимой, чтобы заставить мониторы работать так, как они должны работать, тогда этот кеш будет очищен. Почему это разумно сказать, я не знаю. Это приводит, ИМО, к гораздо большему недоразумению, чем к пониманию. –

20

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

Это не гарантировано не быть не-оп, но спецификация позволяет ей быть не -op.Спецификация требует только синхронизации для установления взаимосвязи между двумя потоками, когда два потока синхронизируются на одном и том же объекте, но на самом деле было бы проще реализовать JVM, где идентификация объекта не имеет значения.

Я думал, что синхронизируется-заявление вызовет кэша промывать

Там нет «кэша» в спецификации языка Java. Это концепция, которая существует только в деталях некоторых (ну, O.K., практически всех) аппаратных платформ и реализаций JVM.

+1

Значит, это означает, что спецификация позволяет отложить синхронизацию до тех пор, пока не произойдет другое действие синхронизации на том же мониторе? – yankee

+1

> В спецификации языка Java нет «кеша» На самом деле JLS говорит такие вещи, как «компилятор не должен скрывать записи, кэшированные в регистрах» в [глава 17] (https://docs.oracle.com /javase/specs/jls/se7/html/jls-17.html).Мне кажется, что они используют регистры в качестве кеша, поэтому в JLS есть кеш. – yankee

+1

@Yankee Это объяснение в части памяти, не связанной с моделью памяти. Если вы прочитаете фактическое определение JMM, вы не найдете ссылки на кэш или регистр (за исключением, возможно, не нормативных пояснительных частей). – Voo

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