Опыт использования примитивов синхронизации потоков
Что проверяют
На Android многопоточность не абстрактная тема: главный поток обязан отдать кадр за миллисекунды, и любая блокировка на нём это видимый фриз. Слушают, отличаете ли вы блокирующие примитивы Java от приостанавливающих примитивов корутин и помните ли про видимость изменений между потоками.
Как отвечать
Разделите ответ на два слоя. Классика, которая остаётся в любом проекте: synchronized на выделенном объекте-мониторе, ReentrantLock, когда нужен таймаут или прерываемое ожидание, Semaphore для ограничения числа одновременных операций, CountDownLatch, чтобы дождаться нескольких задач. Отдельно проговорите @Volatile: он даёт видимость записи другим потокам, но не атомарность, поэтому инкремент делают через AtomicInteger или под блокировкой.
В коде на корутинах блокирующие примитивы вредны: они занимают поток из пула, пока корутина ждёт. Там работают kotlinx.coroutines.sync.Mutex с withLock и одноимённый Semaphore, которые приостанавливают корутину, а не поток. Чаще же состояние просто держат в MutableStateFlow и меняют через update, и отдельная блокировка не нужна. Принадлежность к потоку задаётся диспетчером: диск и сеть уходят в withContext(Dispatchers.IO), работа с UI остаётся на главном.
private val mutex = Mutex()
suspend fun add(item: Item) = mutex.withLock { cache += item }Где спотыкаются
Защищают запись и забывают про чтение. Метод-инкремент берёт блокировку, а геттер читает поле напрямую: атомарность есть, видимости нет, и другой поток может долго наблюдать устаревшее значение. Читатель обязан брать ту же блокировку либо поле должно быть атомарным или помеченным @Volatile.
Блокируют главный поток. runBlocking, ожидание результата задачи или обращение к базе прямо в обработчике нажатия дают подвисание интерфейса, а при задержке в несколько секунд системный диалог о зависании.
Синхронизируются на неподходящем объекте: synchronized(this) в публичном классе или блокировка на строковом литерале. Литерал интернируется и общий на всё приложение, а на this может захватить лок посторонний код. Заводят приватный val lock = Any() и держат его внутри класса.