Почему этот код заканчивается, если геттер помечен как синхронизированный?
Почему этот код успешно завершается, когда метод get()
помечен как синхронизированный несмотря на то, что значение поля не является изменчивым? Без синхронизации он работает бесконечно на моей машине (как и ожидалось).
public class MtApp {
private int value;
/*synchronized*/ int get() {
return value;
}
void set(int value) {
this.value = value;
}
public static void main(String[] args) throws Exception {
new MtApp().run();
}
private void run() throws Exception {
Runnable r = () -> {
while (get() == 0) ;
};
Thread thread = new Thread(r);
thread.start();
Thread.sleep(10);
set(5);
thread.join();
}
}
3 ответа
@Анди Тернер частично прав.
Дополнение synchronized
на get()
Этот метод влияет на требования к видимости памяти и заставляет компилятор (JIT) генерировать другой код.
Однако, строго говоря, должно произойти , прежде чем отношения, соединяющие set(...)
вызов и get()
вызов. Это означает, что set
метод должен быть synchronized
так же как get
(если вы собираетесь сделать это так!).
Короче говоря, версия вашего кода, на которой вы работали, НЕ гарантированно будет работать на всех платформах и при любых обстоятельствах. На самом деле вам повезло!
Читая между строк, кажется, что вы пытаетесь на экспериментальной основе понять, как работает модель памяти Java. Это не очень хорошая идея. Проблема заключается в том, что вы пытаетесь провести обратный инжиниринг чрезвычайно сложного черного ящика без достаточного количества "входных параметров" 1, чтобы вы могли варьировать, чтобы охватить все потенциальные аспекты поведения черных ящиков.
В результате подход "обучение через эксперимент" может оставить вас с неполным или ошибочным пониманием.
Если вы хотите получить полное и точное понимание, начните с чтения модели памяти Java в хорошем учебнике... или самой JLS. Во что бы то ни стало, используйте эксперименты, чтобы попытаться подтвердить свое понимание, но вы должны знать, что JMM определяет (гарантирует) только то, что происходит, если вы поступаете правильно. Если вы поступите неправильно, ваш код может работать в любом случае... в зависимости от всевозможных факторов. Следовательно, часто бывает трудно получить экспериментальное подтверждение того, что определенный способ ведения дел является правильным или неправильным 2.
1 - Некоторые параметры, которые вам понадобятся, на самом деле не существуют. Например, тот, который позволяет вам запускать Java N для N > 12, или тот, который позволяет вам работать на оборудовании, к которому у вас нет доступа... или которого еще нет.
2 - Как показано на вашем примере. Вы получаете "правильный" ответ, даже если код неправильный.
Синхронизация сил this.value = value
случиться раньше get()
,
Это обеспечивает видимость обновленного значения.
Без синхронизации такой гарантии нет. Это может сработать, а может и нет.
Для начала либо value
должно быть volatile
или оба get
а также set
нужно быть synchronized
чтобы это было правильно.
JLS 17.4.5:
Разблокировка на мониторе происходит перед каждой последующей блокировкой на этом мониторе.
Это возможно для value
быть установленным на 5
до снятия блокировки, которая ставит его перед гранью " случается раньше" и делает ее доступной при следующем получении блокировки.
Следует отметить, что такие гарантии хрупки и, в зависимости от планировщика потока, могут вообще не существовать. На платформах с более слабыми моделями синхронизации вы можете не увидеть те же эффекты, которые вы видите здесь.
Смотрите также: Цикл не видит измененное значение без оператора печати