Почему этот код заканчивается, если геттер помечен как синхронизированный? - PullRequest
0 голосов
/ 08 сентября 2018

Почему этот код успешно завершается, когда метод 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 ]

0 голосов
/ 08 сентября 2018

Для начала, либо value должно быть volatile, либо оба значения get и set должны быть synchronized, чтобы это было правильно.

JLS 17.4.5:

Разблокировка на мониторе происходит перед каждой последующей блокировкой на этом мониторе.

Возможно установить value на 5 до снятия блокировки, что ставит его перед краем произойдет до и делает его доступным при следующем получении блокировки .

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


Смотри также: Цикл не видит измененное значение без оператора печати

Странное поведение потока Java, связанного с System.out

0 голосов
/ 09 сентября 2018

@ Энди Тернер частично прав.

Добавление synchronized к методу get() влияет на требования к видимости памяти и приводит к тому, что компилятор (JIT) генерирует другой код.

Однако, строго говоря, должна быть , прежде чем связывает вызов set(...) и вызов get(). Это означает, что метод set должен быть synchronized, а также get (если вы собираетесь сделать это таким образом!).

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


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

В результате подход «обучение через эксперимент» может оставить вас с неполным или ошибочным пониманием.

Если вы хотите получить полное и точное понимание, вы должны начать , читая о модели памяти Java в хорошем учебнике ... или самом JLS. Во что бы то ни стало, используйте эксперименты, чтобы попытаться подтвердить свое понимание, но вы должны знать, что JMM определяет (гарантирует) только , что происходит, если вы поступаете правильно. Если вы поступите неправильно, ваш код может работать в любом случае ... в зависимости от всевозможных факторов. Следовательно, часто трудно получить экспериментальное подтверждение того, что определенный способ ведения дел является правильным или неправильным 2 .

1 - Некоторые параметры, которые вам понадобятся, на самом деле не существуют. Например, тот, который позволяет вам запускать Java N для N> 12, или тот, который позволяет вам работать на оборудовании, к которому у вас нет доступа ... или которого еще нет.

2 - Как показано на вашем примере. Вы получаете «правильный» ответ, даже если код неправильный.

0 голосов
/ 08 сентября 2018

Силы синхронизации this.value = value до происходят раньше get().

Обеспечивает видимость обновленного значения.

Без синхронизации такой гарантии нет. Это может работать, а может и нет.

...