Какие инструменты использовать для решения гонки данных при добавлении элементов в лист из разных потоков
Что проверяют
Понимаете ли вы, почему ArrayList ломается при записи из нескольких потоков, и умеете ли выбрать средство под сценарий, а не одно на все случаи. Для Android следом почти всегда спрашивают, как решить ту же задачу в корутинах и чем Mutex отличается от synchronized.
Как отвечать
Сначала симптом: ArrayList и котлиновский mutableListOf() не потокобезопасны. Одновременные add теряют элементы, оставляют null или падают с ArrayIndexOutOfBoundsException. Дальше варианты:
synchronizedилиReentrantLock: общий замок на все операции со списком. Просто и надёжно,ReentrantLockдобавляетtryLockс таймаутом;Collections.synchronizedList: синхронизирует отдельные вызовы, но обход и составные операции вроде «проверить и добавить» всё равно оборачивают вsynchronized(list);CopyOnWriteArrayList: копирует массив при каждой записи. Хорош для редких изменений и частого чтения, например для списка слушателей, и плох для частыхadd;- очереди: для схемы производитель и потребитель лучше подходят
ConcurrentLinkedQueueилиLinkedBlockingQueue; - корутины:
MutexсwithLockприостанавливает корутину, а не блокирует поток. Другой путь: ограничить доступ одним потоком черезlimitedParallelism(1)или отправлять элементы вChannelс единственным потребителем; - неизменяемое состояние:
MutableStateFlowсupdate { it + item }атомарно заменяет список новым, и UI получает его без дополнительной синхронизации.
Выбор объясняйте через нагрузку: сколько записей, сколько чтений, нужна ли атомарность нескольких шагов.
Где спотыкаются
Считают, что @Volatile решает проблему. Он даёт видимость ссылки, но не делает add атомарной.
Ставят synchronized вокруг приостанавливающего кода в корутине. Компилятор не даст вызвать suspend-функцию в критической секции, а блокировка потока на Dispatchers.Default отнимает его у других корутин.
Предлагают ConcurrentHashMap как замену списку или CopyOnWriteArrayList для списка, куда постоянно пишут. Первое меняет структуру данных, второе копирует весь массив на каждое добавление.