Что такое гонка потоков
Что проверяют
Знаете ли вы, что гонка это не только про атомарность, но и про видимость, и умеете ли применить это в приложении, где есть главный поток, пулы и корутины. Ответ «поставлю synchronized» закрывает вопрос ровно наполовину.
Как отвечать
Гонка возникает, когда результат зависит от порядка, в котором планировщик переключит потоки, а общее состояние при этом меняется без синхронизации. Проблемы две:
- атомарность:
count++это три операции, и два потока теряют инкремент; - видимость: без синхронизации поток вправе сколь угодно долго читать значение из своего кэша, поэтому цикл ожидания по обычному полю может не завершиться никогда.
Модель памяти закрывает обе половины через отношение «происходит раньше»: synchronized, @Volatile, классы из java.util.concurrent.atomic, потокобезопасные коллекции.
В корутинах правила те же. Dispatchers.Default и Dispatchers.IO это пулы потоков, поэтому изменяемое поле во вью-модели остаётся общим. Работают три приёма: не держать изменяемое состояние вовсе, менять поток состояния через update с атомарным сравнением и заменой, брать Mutex из корутин там, где нужна критическая секция без блокировки потока.
Где спотыкаются
Сводят тему к атомарности и забывают про видимость. Атомарный счётчик не поможет флагу отмены, который читают в цикле, а @Volatile не поможет составной операции: инструменты закрывают разные половины задачи.
Присваивают состояние экрана чтением текущего значения и записью изменённой копии из нескольких корутин. Параллельные обновления затирают друг друга, поэтому у потока состояния есть update. Той же природы postValue, который доставляет наблюдателю только последнее значение.
Лечат блокировками то, что является гонкой жизненного цикла. Ответ, пришедший после закрытия экрана, двойной клик, повторный запрос при повороте лечатся областью корутины и её отменой, дебаунсом и идемпотентным ключом запроса, а не ещё одним замком.