Какие инструменты существуют для решения проблем конкурентного доступа к коллекциям в Java
Что проверяют
Понимаете ли вы, чем потокобезопасность отдельной операции отличается от атомарности составного действия, и как устроены конкурентные коллекции. Следом почти всегда спрашивают, как устроен ConcurrentHashMap и почему synchronizedList не спасает при итерации.
Как отвечать
- обёртки
Collections.synchronizedListиsynchronizedMap: каждый метод под одним общим монитором. Отдельные вызовы безопасны, но итерацию по документации нужно окружатьsynchronized (list), иначе поведение не определено, а связка «проверил и положил» не атомарна; ConcurrentHashMap: с Java 8 без сегментов: запись в пустую корзину через CAS, иначе блокируется её первый узел, чтение без блокировок. Атомарные составные операции:putIfAbsent,computeIfAbsent,compute,merge. Итераторы слабо согласованные и не бросаютConcurrentModificationException,nullв ключах и значениях запрещён,size()под нагрузкой приблизителен;CopyOnWriteArrayList: каждая запись копирует массив, итератор работает по снимку. Подходит для списков слушателей, где запись редкость;- очереди:
ConcurrentLinkedQueueбез блокировок;BlockingQueue(ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue) для схемы производитель и потребитель,putиtakeждут,offerиpollвозвращаются сразу или ждут заданный таймаут; - неизменяемость:
List.copyOfиMap.ofснимают проблему совсем, аCollections.unmodifiableListлишь обёртка над изменяемым списком; - явные блокировки и атомики:
ReentrantReadWriteLockдля частого чтения,LongAdderдля счётчиков.
Где спотыкаются
Пишут на ConcurrentHashMap связку if (!map.containsKey(k)) map.put(k, v). Каждый вызов безопасен, а пара нет: между ними другой поток успеет вставить своё значение. Нужен putIfAbsent или computeIfAbsent.
Берут CopyOnWriteArrayList для частой записи. Каждая вставка копирует весь массив, и на больших списках это съедает и CPU, и память.
Внутри computeIfAbsent обращаются к той же карте или делают долгую операцию. Корзина заблокирована на всё время функции, а рекурсивное изменение может закончиться IllegalStateException: Recursive update.