Что такое потокобезопасность
Что проверяют
Различаете ли вы атомарность и видимость и знаете ли, что именно даёт модель памяти Java. Формулировку определения услышать легко, а вот способность выбрать между неизменяемым объектом, атомарной переменной и блокировкой показывает реальный опыт многопоточного кода.
Как отвечать
Класс потокобезопасен, если остаётся корректным при обращении из нескольких потоков без дополнительной синхронизации со стороны вызывающего кода. За этим стоят две разные гарантии: операция не рвётся посередине и результат виден другому потоку.
Способы по возрастанию цены: не разделять состояние вовсе (локальные переменные, ThreadLocal); сделать объект неизменяемым, то есть поля final и никаких сеттеров; заменить составное действие атомарным (AtomicLong, LongAdder при высокой конкуренции); закрыть критическую секцию через synchronized или ReentrantLock; взять готовую структуру из пакета конкурентных утилит.
Отдельно про volatile: он даёт видимость и запрет переупорядочивания, но не атомарность. Инкремент такого поля всё равно теряет обновления, потому что это три операции: чтение, сложение, запись.
Хорошо добавить про отношение happens before. Выход из синхронизированного блока и запись в volatile поле устанавливают его, и благодаря этому второй поток видит не только саму переменную, но и все записи, сделанные до неё.
Где спотыкаются
Считают конкурентную карту достаточной. Безопасна каждая отдельная операция, а связка «проверил, потом положил» из двух вызовов снова гонка, поэтому есть putIfAbsent, computeIfAbsent и merge.
Синхронизируют разные мониторы и думают, что защитили общее состояние. Синхронизированный метод экземпляра берёт блокировку на объекте, статический на объекте класса, и друг от друга они не защищают.
Берут список с копированием при записи под частую запись. Он хорош, когда читают намного чаще, чем пишут, потому что каждая запись копирует внутренний массив целиком.