Мьютекс (mutual exclusion) обеспечивает взаимное исключение: захватил — работаешь, остальные ждут; отпустил — один из ждущих заходит. У мьютекса есть владелец: отпустить его должен тот же поток. Бывают реентерабельные — поток может захватить свой же замок повторно (synchronized и ReentrantLock в Java, RLock в Python), иначе рекурсивный вызов заблокирует сам себя.
Семафор — счётчик. acquire уменьшает его и ждёт, если счётчик ноль; release увеличивает и будит ждущего. Владельца нет: освободить может любой поток. Применения:
- ограничение параллелизма — не больше N одновременных запросов к базе или внешнему API, N соединений в пуле;
- сигнализация — один поток делает
release, другой ждёт на acquire (семафор с нулём); - двоичный семафор (N = 1) похож на мьютекс, но из-за отсутствия владельца легко ошибиться.
Монитор — объект, в котором мьютекс объединён с условными переменными: поток в критической секции может ждать условия (wait), временно отпуская замок, а другой — разбудить его (notify). В Java каждый объект — монитор: synchronized + wait/notify; современнее — ReentrantLock + Condition. Ждать условия надо в цикле while (!условие) wait() — бывают ложные пробуждения, и пока поток просыпался, условие мог перехватить другой.
Read-write lock — много читателей одновременно или один писатель (ReentrantReadWriteLock). Выгоден, когда чтений намного больше и критическая секция заметная; иначе накладные расходы съедают выигрыш. StampedLock добавляет оптимистичное чтение без блокировки.
Цена. Свободный замок захватывается атомарной инструкцией за десятки наносекунд. Занятый — поток засыпает и просыпается через ядро (переключение контекста), это микросекунды. Поэтому критические секции делают короткими и не делают внутри них ввод-вывод.