Какие альтернативы synchronized существуют в Kotlin корутинах
Что проверяют
Понимаете ли вы разницу между блокировкой потока и приостановкой корутины. synchronized держит поток, а поток в корутинном коде это общий ресурс: заблокировали один из потоков Dispatchers.Default и подвесили чужие задачи. Дальше обычно спрашивают, почему нельзя просто обернуть suspend-вызов в synchronized.
Как отвечать
- Mutex:
kotlinx.coroutines.sync.MutexиwithLock { }. Ожидание приостанавливает корутину, а не поток, внутри секции разрешены suspend-вызовы. Не реентерабельный: повторный захват из той же корутины это дедлок; - Semaphore: оттуда же, ограничивает число одновременно работающих корутин, например
Semaphore(10)на запросы к внешнему API; - однопоточное ограничение:
Dispatchers.Default.limitedParallelism(1)иwithContextна нём. Изменения состояния идут строго по очереди, блокировка не нужна. Это замена устаревшемуnewSingleThreadContext; - изоляция состояния:
Channelкак очередь команд, её разбирает одна корутина-владелец. Билдерactorрешает ту же задачу, но помечен как устаревающий API; - атомарные обновления:
MutableStateFlow.update { }(внутри цикл CAS), классы изjava.util.concurrent.atomic, либоatomicfuв мультиплатформенном коде; - неизменяемость: самый дешёвый вариант, когда состояние заменяют целиком, а не правят по месту.
Про запрет: компилятор Kotlin не даёт разместить точку приостановки внутри synchronized. Монитор принадлежит потоку, а корутина после приостановки может продолжиться на другом, и освобождать монитор будет некому.
Выбор по ситуации: одно поле это атомарный тип или StateFlow, несколько связанных полей и suspend-вызовы в секции это Mutex, сложный протокол с многими писателями это корутина с каналом.
Где спотыкаются
Внутри withLock делают долгий сетевой вызов. Формально корректно, фактически очередь: остальные корутины ждут не данные, а сеть.
Считают, что withContext(Dispatchers.IO) защищает состояние. Это смена пула, а не синхронизация: у IO по умолчанию 64 потока, гонка никуда не делась.
Захватывают тот же Mutex вложенно, когда публичный метод зовёт другой публичный. Вместо повторного входа получают вечное ожидание.