Какие сложности возникают при одновременном доступе нескольких потоков к одной переменной
Что проверяют
Понимаете ли вы, что проблема не только в атомарности, но и в видимости изменений между потоками.
Как отвечать
Три разных класса проблем.
Гонка при чтении и записи. count++ это три операции: чтение, увеличение, запись. Два потока могут прочитать одно значение и записать одинаковый результат, потеряв одно увеличение.
Видимость. У каждого потока свой кэш процессора, и без синхронизации записанное одним потоком может неопределённо долго не быть видно другому. Классический пример это цикл while (!stopped), который не завершается никогда, потому что поток читает своё закэшированное значение. Именно от этого спасает volatile: он даёт видимость и запрещает переупорядочивание, но не даёт атомарности, поэтому volatile int для счётчика не работает.
Переупорядочивание. Компилятор и процессор вправе менять порядок операций, если это не видно однопоточно. Отсюда классическая поломка ленивой инициализации с двойной проверкой без volatile: другой поток может увидеть ссылку на ещё не до конца сконструированный объект.
Правила задаёт модель памяти Java через отношение «происходит раньше»: выход из synchronized и запись в volatile образуют барьер, а старт и завершение потока тоже.
Инструменты по возрастанию стоимости: неизменяемость и локальные переменные (проблемы нет вовсе), volatile для флага, атомарные классы (AtomicInteger, LongAdder при высокой конкуренции), synchronized и ReentrantLock для составных операций, конкурентные коллекции.
Где спотыкаются
Считают volatile заменой синхронизации.
Синхронизируют каждый метод по отдельности и всё равно получают гонку на составной операции «проверил и сделал».
Не знают про видимость и объясняют всё только атомарностью.
Пишут двойную проверку без volatile.