Когда проводила собеседования, то заметила, что многие говорят именно про Mutex. Я его раньше его не использовала, поэтому стало интересно разобраться.
Что такое Mutex?
Mutex (Mutual Exclusion — взаимное исключение) — это инструмент для синхронизации, который гарантирует, что только один поток будет выполнять определенный участок кода в одно и то же время.
Как устроено: внутри Mutex есть флаг, который указывает, занят ли ресурс или нет. Если поток пытается захватить уже занятый Mutex, он добавляется в некую очередь ожидания. Когда поток, удерживающий блокировку, завершает работу, он освобождает блокировку, устанавливая флаг обратно в какое-то значение и следующий поток из очереди начинает работу.
Пример:
Плюсы:
- Простота использования: Mutex интуитивно понятен и в нём не нужно особо разбираться;
- Гарантия безопасности: только один поток может войти в критическую секцию. Если мы какой-то кусочек кода поместили в withLock, то в этом кусочке кода всегда будет только один поток, а остальные будут ждать свою очередь.
Минусы:
- Нужна дополнительная зависимость implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'. Я понимаю, что во многих проектах она и так есть, но не везде;
- Функция должна быть suspend. Это спорный минус, но я обычно использую такие инструменты при работе с базами данных или префами и там не всегда есть suspend;
- Если участок кода заблокирован, то он недоступен даже для чтения;
- Потеря производительности: если участок кода часто блокируется, это может стать проблемой (слишком долго ждать).
- Deadlocks: если несколько мьютексов захвачены в неправильном порядке, это может привести к взаимной блокировке.
- Низкоприоритетный поток может "заблокировать" высокоприоритетный поток, если захватит мьютекс перед ним.
Что такое ReadWriteLock?
ReadWriteLock — это более гибкий инструмент для синхронизации. Внутри ReadWriteLock есть две отдельные блокировки: одна для чтения и одна для записи. Они разрешают множеству потоков читать данные одновременно, но при записи они работают так же, как и Mutex — только один поток может записывать данные.
Примерный алгоритм работы:
- Захват на чтение: если нет потоков, которые что-то записывают, и очередь ожидания записывающих потоков пуста, то новый поток может захватить блокировку для чтения. Заметьте, что если уже есть активные читатели, то всё в порядке и новый поток сможет прочитать данные. Счетчик читателей увеличивается. Если сейчас идёт запись, то поток добавляется в ожидающую очередь для чтения.
- Захват на запись: поток может захватить блокировку для записи, только если нет активных читателей и писателей. Счетчик писателей увеличивается.
- Освобождение: при освобождении блокировки соответствующий счетчик уменьшается. Если счетчик достигает нуля, блокировка освобождается, и ожидающие потоки могут попытаться её захватить.
Пример:
Плюсы:
- Повышенная производительность: позволяет множеству потоков одновременно читать данные.
- Гибкость: легко адаптировать под различные сценарии.
- Блокировка с тайм-аутом: В отличие от обычных мьютексов, ReadWriteLock может дать возможность попытаться захватить блокировку с определенным тайм-аутом.
Минусы:
- Сложность: необходимо правильно реализовать механизмы чтения и записи. Тут у нас уже не один withLock на всё. Нужно тщательно продумывать какие методы можно оставить доступными для чтения, а какие блокировать полностью.
- Голодание: если чтение происходит значительно чаще записи, существует риск, что потоки, ожидающие записи, будут "голодать", т.е. очень долго ждать своей очереди и когда блокировка снимется. Некоторые реализации решают эту проблему через механизмы приоритетов или "честности" (обрабатываем в том порядке, в каком обращались).
Дубль статей в телеграмме — https://t.me/android_junior
Мои заметки в телеграмме — https://t.me/android_junior_notes
P.S. сделано с помощью ChatGPT и Midjourney. :)