Почему потоку нужно копировать объект в локальную память для работы с ним
Что проверяют
Знаете ли вы модель памяти JVM, а не только слово «синхронизация». За «копированием в локальную память» скрываются две разные вещи: регистры и кеш процессора с одной стороны, защитная копия объекта в коде с другой. Интервьюер хочет услышать обе и понять, различаете ли вы видимость и атомарность.
Как отвечать
Первый слой это модель памяти. Поток не обязан каждый раз читать поле из общей памяти: и процессор, и компилятор вправе держать значение в регистре или кеше ядра. Без отношения happens-before запись одного потока может не стать видимой другому никогда, и цикл ожидания флага повиснет навсегда.
@Volatile private var running = true // без аннотации поток может не увидеть falseВидимость дают @Volatile, synchronized, атомарные типы, а в корутинах Mutex и каналы.
Второй слой прикладной. Копию делают, чтобы работать со снимком: пока один поток перебирает коллекцию, другой её меняет, и прилетает ConcurrentModificationException. Поэтому наружу отдают неизменяемую копию, а состояние экрана обновляют новым объектом: StateFlow сравнивает значения и равное не эмитит, DiffUtil тоже сличает старый и новый списки, так что мутация на месте оставляет интерфейс без обновления.
Где спотыкаются
Считают @Volatile заменой блокировке. Аннотация отвечает только за видимость, инкремент так и остаётся тремя действиями, и два потока спокойно теряют одно из них. Для счётчика нужен AtomicInteger или обновление под замком.
Второе: копия оказывается поверхностной. Метод copy() у data class и toList() переносят ссылки, вложенные изменяемые объекты остаются общими, и гонка возвращается через них. Спасают неизменяемые типы, а не ещё один уровень копирования.
Третье: копируют всё подряд. Снимок большого списка на каждый кадр отрисовки бьёт по памяти и заставляет сборщик мусора работать чаще. Там, где нужны только чтения, дешевле неизменяемая структура или заранее подготовленные данные, чем копия в каждом обработчике.