Volatile-массив публикует ссылку, а не содержимое

Кандидат объявляет поле private volatile int[] cache; и говорит: теперь другие потоки всегда видят актуальные данные массива. Это наполовину верно, и собеседующий обычно цепляется именно за эту половину.

volatile гарантирует видимость самой ссылки на массив между потоками: если один поток присвоил полю новый массив, другой увидит именно его, а не устаревшую ссылку. Но изменение элемента внутри уже опубликованного массива — cache[3] = 5; — это запись не в volatile-поле, а в обычную ячейку массива. На неё happens-before от volatile не распространяется.

private volatile int[] cache = new int[10];

// видно другим потокам сразу — это запись в volatile-поле void replace(int[] fresh) { cache = fresh; }

// НЕ гарантированно видно другим потокам сразу — // это запись в элемент массива, а не в поле cache void mutate(int i, int v) { cache[i] = v; }

Следующий вопрос обычно про то же самое, но с коллекциями: "а если вместо массива у вас volatile List, поможет ли это от гонки при add()?" Ответ тот же — volatile защищает замену ссылки на список, но не защищает мутацию содержимого самого списка, и ArrayList под конкурентным add() всё равно сломается. Для безопасной мутации нужен либо immutable-объект с полной заменой ссылки, либо честная синхронизация, либо потокобезопасная коллекция.

Если структура мутируется на месте, volatile на поле — это защита только половины сценария; вторая половина требует либо неизменяемости, либо блокировки.

Тренажёр: 900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки