В следующем коде:Почему `synchronized (new Object()) {}` a no-op?
class A {
private int number;
public void a() {
number = 5;
}
public void b() {
while(number == 0) {
// ...
}
}
}
Если метод Ь называется, а затем новый поток запускается, который запускает метод а, то метод б не гарантируется когда-либо увидеть изменение number
и, таким образом, b
никогда не может прекратить ,
Конечно, мы можем сделать number
volatile
, чтобы решить эту проблему. Однако по академическим причинам давайте предположим, что 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
для прекращения гарантии?
Ссылка не сохраняется, поэтому ни один другой поток не может ждать этой ссылки. Что вы пытаетесь защитить? –
Этот вопрос прекрасно иллюстрирует причину, по которой я призываю людей не пытаться объяснить или понять семантику языка с точки зрения реализации конкретных вещей, которые могут или не могут существовать, такие как вымышленный «кеш локального процессора». –
@DavidSchwartz Точно, и на самом деле фраза «очистка кеша», особенно когда в непосредственной близости от фразы «основная память», может быть очень обманчивой относительно того, что на самом деле происходит. Протоколы когерентности кэша часто обеспечивают согласованность памяти, фактически не доходя до медленной основной памяти. И если вы откажетесь от JLS и JMM и попробуете рассуждать о базовой архитектуре, вам нужно учитывать такие вещи, как тот факт, что некоторые механизмы синхронизации не обеспечивают согласованность глобальной памяти (продемонстрировано IRIW). Итак, как сказал ишавит, придерживаться JLS - путь. –