видимость значения при синхронизированном изменении

107
03 марта 2021, 22:50

Насколько известно, изменив volatile переменную, мы остаёмся уверенны, что остальные потоки, которые будут её читать, получат новое значение. Причина тому ясна: значения переменных не кэшируются (конечно, там больше происходящего, но сейчас важна суть). Так вот мне стало неясно, а что происходит с видимостью изменений non-volatile переменных при синхронизации?

В достоверном источнике сказано следующее:

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

Так вот, вопрос: а как это случается, почему же ключевое слово volatile нам теперь не нужно? Нам нужны synchronized getter'ы и setter'ы, али же достаточно синхронизировано прочитать? А может только записать?

Поясню часть вопроса про getter'ы и setter'ы:
Пусть есть код:

class A {
    int n=1;
    public void change(){
        while (!Thread.interrupted())
            synchronized (this){
                n=3*n+4;
            }
    } 
}

в main(String[] args) того же пакета, что и класс A:

A a = new A();
new Thread(a::change).start();
TimeUnit.SECONDS.sleep(10);
System.out.println(a.n);

И здесь возникает вопрос.
Давайте сейчас забудем про то, что переменная n могла переполниться, а потом через наш sleep в 10 секунд снова стать равной 1. И забудем про то, что когда мы читаем n она может быть в некотором "неустойчивом" состоянии.
Так вот, достаточно ли было синхронизации в методе change()? Гарантирует ли она проталкивание в main memory нашей переменной n? Т.е. по факту вопрос такой: правда ли, что не будет такого, что через 10 секунд сна доставая a.n мы получим там 1, ибо значение этого поля пока лежит в кэше?

А если изменить последнюю строчку в main(String[] args) на:

synchronized (a){
    System.out.println(a.n);
}

Теперь точно всё хорошо и проблем с видимостью изменений не будет? Как так устроен этот аспект механизма синхронизации? Т.е. change() не проталкивал в main memory, а при синхронизированном чтении сразу проталкивалось всё?... Или синхронизации в change() было достаточно?

В приложение к ответу буду очень благодарен увидеть цитаты из документации (с переводом на русский язык) к Java с ссылкой на них.

Здеся и тута есть важные мысли, но я хочу получить ответ на всё-таки другой вопрос.

Answer 1

Модификаторы volatile и synchronized решают ведь не только проблему с кэшированием. Есть ещё масса нюансов, которые требуется учитывать в многопоточном программировании. Например, reordering. Есть ряд случаев, в которых доступ к переменным (полям объектов, статическим полям и элементам массива) может выполниться в порядке, отличном от указанного в программе. Компилятор свободен в расположении инструкций с целью оптимизации. Процессоры могут выполнять инструкции в ином порядке в ряде случаев. Данные могут перемещаться между регистрами, кэшами процессора и оперативной памятью в порядке, отличном от указанного в программе. Но JMM гарантирует сохранение отношений happens-before внутри синхронизированных блоков, устанавливая барьер при захвате и освобождении блокировки. Сама семантика слова "синхронизация" говорит за себя. Поток осуществляющий синхронизированный доступ к переменной, синхронизирует своё состояние. Но действительно актуальное состояние переменной для всех возможно только в том случае, если синхронизируются все.

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

P.S. Вместо ссылок на документацию приведу ссылки на доклады человека, чьё имя стало нарицательным в этой области.

Aleksey Shipilёv - Java Memory Model Pragmatics, part 1
Aleksey Shipilёv - Java Memory Model Pragmatics, part 2
Алексей Шипилёв — Близкие Контакты JMM-степени

READ ALSO
Почему при записи файла файл пустой?

Почему при записи файла файл пустой?

файл сохраняется но он пустойпри дебаге выяснилось что output пустой

127
Почему не работает click()?

Почему не работает click()?

Всё хорошо работает до выполнения click()Выдаёт исключение: org

101
React и серверный рендеринг

React и серверный рендеринг

Пишу проекты на ReactПериодически требуется рендерить для ботов SPA на сервере

94
Как подключить файл из другой подпапки

Как подключить файл из другой подпапки

Как подключить таблицу стилей из другой папки ? Уже все перепробовал и так import "/сss/bootstrap

102