Какие инструменты и механизмы используются для обеспечения потокобезопасности
Что проверяют
Понимаете ли вы, что потокобезопасность складывается из атомарности, видимости и порядка операций, и умеете ли подобрать механизм под задачу. Следом обычно просят объяснить разницу между volatile и synchronized или найти гонку в коротком фрагменте кода.
Как отвечать
Самый надёжный вариант: не делить изменяемое состояние между потоками. Дальше по нарастающей:
- неизменяемость и изоляция:
final-поля, records,List.of, данные в локальных переменных илиThreadLocal; volatile: видимость и запрет переупорядочивания, но не атомарность. Годится для флага остановки, не годится дляcount++;- атомики:
AtomicInteger,AtomicReferenceна CAS без блокировок,LongAdderдля счётчиков под высокой конкуренцией; synchronized: монитор объекта, реентерабельный, даёт happens-before между выходом из блока и следующим входом на том же мониторе;- блокировки из
java.util.concurrent.locks:ReentrantLockсtryLockпо таймауту,lockInterruptiblyи несколькимиCondition,ReentrantReadWriteLockдля частого чтения,StampedLockс оптимистичным чтением, но без реентерабельности; - конкурентные коллекции:
ConcurrentHashMapс атомарнымиcomputeIfAbsentиmerge,CopyOnWriteArrayListдля редких записей,BlockingQueueдля схемы производитель и потребитель; - синхронизаторы:
CountDownLatch,CyclicBarrier,Semaphore,Phaser.
Упомяните виртуальные потоки из Java 21: до Java 24 synchronized закреплял виртуальный поток за несущим, и под нагрузкой советовали ReentrantLock. JEP 491 в Java 24 это ограничение снял. Взаимоблокировки ищут через jstack: дамп потоков прямо сообщает о найденном deadlock.
Где спотыкаются
Лечат гонку на счётчике через volatile. Инкремент состоит из чтения и записи, и два потока всё равно теряют обновления. Нужен AtomicInteger или блокировка.
Пишут check-then-act на ConcurrentHashMap: if (!map.containsKey(k)) map.put(k, v). Каждый вызов атомарен, а связка из двух нет. Правильно putIfAbsent или computeIfAbsent.
Вызывают lock() без try/finally. Исключение внутри оставит блокировку захваченной навсегда, а synchronized такого не допускает.