Как решается проблема атомарности в конкурентной среде
Что проверяют
Понимаете ли вы разницу между атомарностью, видимостью и упорядочиванием и умеете ли подобрать инструмент под конкретную операцию. Следом почти всегда спрашивают, почему volatile int count не спасает count++ и когда AtomicInteger уже недостаточно.
Как отвечать
Начните с причины: count++ это чтение, увеличение и запись, и два потока могут прочитать одно значение. Та же беда с check-then-act: containsKey, а потом put небезопасно даже на ConcurrentHashMap.
Дальше инструменты от лёгких к тяжёлым:
volatile: даёт видимость и запрет переупорядочивания, но не атомарность составной операции. Годится для флага остановки, не для счётчика;- атомарные классы:
AtomicInteger,AtomicLong,AtomicReferenceработают на CAS без блокировки. Сложную логику пишут черезupdateAndGetили цикл сcompareAndSet; LongAdder: счётчик под сильной конкуренцией. Раскладывает значение по ячейкам и выигрывает уAtomicLong, если сумму читают редко;- атомарные методы коллекций:
putIfAbsent,computeIfAbsent,mergeуConcurrentHashMapатомарны для одного ключа; synchronized: взаимное исключение плюс happens-before между освобождением монитора и следующим захватом;ReentrantLock: то же, но сtryLockпо таймауту, прерываемым ожиданием и несколькимиCondition. Для частого чтения естьReadWriteLockиStampedLock.
Отдельно скажите про несколько переменных: два атомика по отдельности не дают атомарности пары. Решение: блокировка или неизменяемый объект в AtomicReference, который меняют целиком через CAS.
Про виртуальные потоки: до JDK 24 блокирующий вызов внутри synchronized закреплял виртуальный поток за несущим, начиная с JDK 24 это ограничение снято.
Где спотыкаются
Отвечают «сделаю поле volatile» на вопрос про счётчик. Видимость появляется, а потерянные инкременты остаются.
Считают, что потокобезопасная коллекция делает атомарной любую цепочку вызовов. Атомарен каждый метод, связка get и put нет.
Забывают про ABA у CAS: значение вернулось к прежнему, и CAS прошёл, хотя между проверками объект менялся. Для таких случаев есть AtomicStampedReference со счётчиком версии.