Приходилось ли работать с многопоточностью, синхронизацией и потокобезопасными коллекциями
Что проверяют
Отличаете ли вы видимость изменений от атомарности и понимаете ли, что именно даёт каждый инструмент. Названная потокобезопасная коллекция сама по себе ничего не доказывает: спросят, какую проблему вы ей закрывали и какую она не закрывает.
Как отвечать
Сначала честно про масштаб опыта: обработка запросов пулом потоков это одно, собственная синхронизация разделяемого состояния совсем другое. Дальше по существу:
synchronizedиReentrantLockдают взаимное исключение и видимость изменений после выхода из блока;volatileгарантирует видимость и запрещает переупорядочивание, но не делаетcount++атомарным: это чтение, инкремент и запись;- счётчики это
AtomicInteger, а при высокой конкуренцииLongAdder; ConcurrentHashMapвместоCollections.synchronizedMap: у него есть атомарныеputIfAbsent,computeIfAbsent,merge, и чтение не блокируется;CopyOnWriteArrayListберут только когда читают часто, а пишут редко: каждая запись копирует массив целиком;BlockingQueueсвязывает производителя и потребителя, задачи отдают вExecutorService, а не в новый поток на каждый запрос.
counts.merge(key, 1L, Long::sum);Про дедлоки скажите сами: единый порядок захвата блокировок, tryLock с таймаутом, никаких вложенных локов и вызовов чужого кода под блокировкой.
Где спотыкаются
Считают, что потокобезопасная коллекция делает потокобезопасным код вокруг неё. Пара get и следом put не атомарна даже у ConcurrentHashMap: между ними успевает вклиниться другой поток, нужна одна атомарная операция.
Второе: ставят volatile на счётчик или на ссылку, внутри которой изменяемый объект. Поле видно всем потокам, но инкременты теряются, а изменения полей внутри объекта никакой гарантии видимости не получают.
Третье: синхронизируются на неудачном мониторе: на строковом литерале, на упакованном числе из кэша или на поле, которое переприсваивают. Отдельная беда это блокировка на всём методе вместе с походом в базу: пул потоков превращается в очередь.